diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f12ab82..4fe45ae 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,6 +40,8 @@ jobs:
run: node scripts/gen-ramps.mjs --check
- name: Build
run: pnpm build
+ - name: Bundle budget
+ run: node scripts/check-bundle.mjs
# The browser tests run against `next dev` on the in-memory mock (no cloud), at 375 and 1280 px.
e2e:
diff --git a/apps/web/e2e/language.spec.ts b/apps/web/e2e/language.spec.ts
index 1f7d640..9b09308 100644
--- a/apps/web/e2e/language.spec.ts
+++ b/apps/web/e2e/language.spec.ts
@@ -67,3 +67,15 @@ test("the account area speaks the language too, and the long words fit", async (
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth);
expect(overflow).toBe(0);
});
+
+test("a public profile carries only its own words, and none is missing", async ({ page }) => {
+ const problems: string[] = [];
+ page.on("console", (m) => {
+ if (m.type() === "error" && /MISSING_MESSAGE|Could not resolve/.test(m.text())) problems.push(m.text());
+ });
+ await page.goto("/u/mira?lang=de");
+ await expect(page.locator("html")).toHaveAttribute("lang", "de");
+ await expect(page.getByRole("heading", { level: 1, name: "mira" })).toBeVisible();
+ await expect(page.getByRole("region").first()).toBeVisible();
+ expect(problems).toEqual([]);
+});
diff --git a/apps/web/messages/de/auth.json b/apps/web/messages/de/auth.json
index 9fe0d1f..c223a89 100644
--- a/apps/web/messages/de/auth.json
+++ b/apps/web/messages/de/auth.json
@@ -25,11 +25,7 @@
"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"
+ "helpLink": "Frag auf Discord"
},
"step": {
"loading": "Wird geladen",
diff --git a/apps/web/messages/de/common.json b/apps/web/messages/de/common.json
index e689a5c..e8dbc84 100644
--- a/apps/web/messages/de/common.json
+++ b/apps/web/messages/de/common.json
@@ -16,5 +16,11 @@
"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."
+ },
+ "legal": {
+ "nav": "Rechtliches",
+ "legal": "Impressum",
+ "privacy": "Datenschutzhinweis",
+ "terms": "Nutzungsbedingungen"
}
}
diff --git a/apps/web/messages/en/auth.json b/apps/web/messages/en/auth.json
index fb8324e..f0126ad 100644
--- a/apps/web/messages/en/auth.json
+++ b/apps/web/messages/en/auth.json
@@ -25,11 +25,7 @@
"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"
+ "helpLink": "Ask on Discord"
},
"step": {
"loading": "Loading",
diff --git a/apps/web/messages/en/common.json b/apps/web/messages/en/common.json
index e7c6bd5..cfac783 100644
--- a/apps/web/messages/en/common.json
+++ b/apps/web/messages/en/common.json
@@ -16,5 +16,11 @@
"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."
+ },
+ "legal": {
+ "nav": "Legal",
+ "legal": "Legal notice",
+ "privacy": "Privacy notice",
+ "terms": "Terms of use"
}
}
diff --git a/apps/web/messages/es/auth.json b/apps/web/messages/es/auth.json
index 0c87adf..2ca639f 100644
--- a/apps/web/messages/es/auth.json
+++ b/apps/web/messages/es/auth.json
@@ -25,11 +25,7 @@
"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"
+ "helpLink": "Pregunta en Discord"
},
"step": {
"loading": "Cargando",
diff --git a/apps/web/messages/es/common.json b/apps/web/messages/es/common.json
index 8fad371..293f71f 100644
--- a/apps/web/messages/es/common.json
+++ b/apps/web/messages/es/common.json
@@ -16,5 +16,11 @@
"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."
+ },
+ "legal": {
+ "nav": "Legal",
+ "legal": "Aviso legal",
+ "privacy": "Aviso de privacidad",
+ "terms": "Condiciones de uso"
}
}
diff --git a/apps/web/messages/fr/auth.json b/apps/web/messages/fr/auth.json
index b56c31d..e185ece 100644
--- a/apps/web/messages/fr/auth.json
+++ b/apps/web/messages/fr/auth.json
@@ -25,11 +25,7 @@
"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"
+ "helpLink": "Demande sur Discord"
},
"step": {
"loading": "Chargement",
diff --git a/apps/web/messages/fr/common.json b/apps/web/messages/fr/common.json
index 214b826..c68a2ea 100644
--- a/apps/web/messages/fr/common.json
+++ b/apps/web/messages/fr/common.json
@@ -16,5 +16,11 @@
"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."
+ },
+ "legal": {
+ "nav": "Informations légales",
+ "legal": "Mentions légales",
+ "privacy": "Avis de confidentialité",
+ "terms": "Conditions d’utilisation"
}
}
diff --git a/apps/web/messages/pt/account.json b/apps/web/messages/pt/account.json
index 8f80d5e..66a2b4f 100644
--- a/apps/web/messages/pt/account.json
+++ b/apps/web/messages/pt/account.json
@@ -151,9 +151,9 @@
"detail": "{platform} · Último uso: {used}",
"signOut": "Sair",
"empty": "Nenhum launcher conectado ainda.",
- "confirmTitle": "Desconectar este launcher?",
+ "confirmTitle": "Sair deste launcher?",
"confirmDescription": "Ele fica offline até alguém entrar de novo nesse PC.",
- "confirmLabel": "Desconectar launcher"
+ "confirmLabel": "Sair do launcher"
},
"browsers": {
"title": "Navegadores",
@@ -164,7 +164,7 @@
"detail": "Entrou {created} · Último uso: {used}",
"signOut": "Sair",
"confirmThisTitle": "Sair deste navegador?",
- "confirmOtherTitle": "Desconectar aquele navegador?",
+ "confirmOtherTitle": "Sair daquele 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"
@@ -174,7 +174,7 @@
"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.",
+ "confirmDescription": "Todo launcher e navegador sai de uma vez, este também.",
"confirmLabel": "Sair em todos os lugares"
}
},
@@ -280,7 +280,7 @@
},
"confirm": {
"title": "Excluir sua conta?",
- "description": "Ela é excluída em 30 dias. Todo launcher e navegador é desconectado agora."
+ "description": "Ela é excluída em 30 dias. Todo launcher e navegador sai agora."
}
},
"compare": {
@@ -341,9 +341,9 @@
"editProfile": "Editar seu perfil",
"blocked": "Bloqueado",
"friends": "Amigos",
- "requestSent": "Solicitação enviada",
+ "requestSent": "Pedido enviado",
"accepting": "Aceitando…",
- "accept": "Aceitar solicitação",
+ "accept": "Aceitar pedido",
"sending": "Enviando…",
"addFriend": "Adicionar amigo",
"addInAnyPlay": "Adicionar amigo no AnyPlay",
diff --git a/apps/web/messages/pt/auth.json b/apps/web/messages/pt/auth.json
index 3d5089f..3453d16 100644
--- a/apps/web/messages/pt/auth.json
+++ b/apps/web/messages/pt/auth.json
@@ -25,11 +25,7 @@
"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"
+ "helpLink": "Pergunte no Discord"
},
"step": {
"loading": "Carregando",
diff --git a/apps/web/messages/pt/common.json b/apps/web/messages/pt/common.json
index d4ab3b6..8963245 100644
--- a/apps/web/messages/pt/common.json
+++ b/apps/web/messages/pt/common.json
@@ -16,5 +16,11 @@
"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."
+ },
+ "legal": {
+ "nav": "Legal",
+ "legal": "Aviso legal",
+ "privacy": "Aviso de privacidade",
+ "terms": "Termos de uso"
}
}
diff --git a/apps/web/messages/tr/account.json b/apps/web/messages/tr/account.json
index 880d855..bb6f9a0 100644
--- a/apps/web/messages/tr/account.json
+++ b/apps/web/messages/tr/account.json
@@ -59,7 +59,7 @@
"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ı}}",
+ "launchersBrowsers": "{launchers, plural, one {# başlatıcı} other {# başlatıcı}} · {browsers, plural, one {# tarayıcı} other {# tarayıcı}}",
"trophiesAudience": "Kupalar: {audience}",
"pictureAudience": "Profil resmi: {audience}",
"audience": {
@@ -78,8 +78,8 @@
"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."
+ "title": "Başlatıcıyı indir",
+ "description": "Bu hesapla başlatıcıya giriş yap; arkadaşların, partilerin ve kupaların orada olur."
}
},
"profile": {
@@ -102,7 +102,7 @@
},
"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.",
+ "description": "En fazla {max}, oyun kimliğiyle. Bu site kimliği gösterir. Başlatıcı 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",
@@ -117,7 +117,7 @@
}
},
"security": {
- "lead": "Nasıl giriş yaptığın ve hesabına giriş yapmış her Launcher ile tarayıcı.",
+ "lead": "Nasıl giriş yaptığın ve hesabına giriş yapmış her başlatıcı 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.",
@@ -146,14 +146,14 @@
"thisDevice": "bu cihaz"
},
"devices": {
- "title": "Launcher’lar",
- "description": "Launcher’ın hesabınla giriş yaptığı tüm bilgisayarlar.",
+ "title": "Başlatıcılar",
+ "description": "Hesabınla başlatıcıya giriş yapılmış 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ı?",
+ "empty": "Henüz hiçbir başlatıcıda giriş yapılmadı.",
+ "confirmTitle": "Bu başlatıcıdan çıkış yapılsın mı?",
"confirmDescription": "O bilgisayarda biri tekrar giriş yapana kadar çevrimdışı olur.",
- "confirmLabel": "Launcher’dan çıkış yap"
+ "confirmLabel": "Başlatıcıdan çıkış yap"
},
"browsers": {
"title": "Tarayıcılar",
@@ -171,10 +171,10 @@
},
"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.",
+ "description": "Bu dahil her başlatıcı 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.",
+ "confirmDescription": "Bu dahil her başlatıcı ve tarayıcıdan aynı anda çıkış yapılır.",
"confirmLabel": "Her yerden çıkış yap"
}
},
@@ -182,7 +182,7 @@
"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."
+ "description": "Değişiklikler hemen geçerli olur, başlatıcıda da."
},
"parts": {
"trophies": {
@@ -215,13 +215,13 @@
},
"appearOffline": {
"title": "Çevrimiçi durumu",
- "description": "Hesabına giriş yapmış her Launcher ve tarayıcı için geçerlidir.",
+ "description": "Hesabına giriş yapmış her başlatıcı 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.",
+ "description": "Sen açana kadar kapalıdır. Hesabına giriş yapmış her başlatıcı 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}"
@@ -280,7 +280,7 @@
},
"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."
+ "description": "Hesap 30 gün içinde silinir. Her başlatıcı ve tarayıcıdan şimdi çıkış yapılır."
}
},
"compare": {
diff --git a/apps/web/messages/tr/auth.json b/apps/web/messages/tr/auth.json
index bdcb551..3e3c7a1 100644
--- a/apps/web/messages/tr/auth.json
+++ b/apps/web/messages/tr/auth.json
@@ -25,11 +25,7 @@
"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ı"
+ "helpLink": "Discord’da sor"
},
"step": {
"loading": "Yükleniyor",
diff --git a/apps/web/messages/tr/common.json b/apps/web/messages/tr/common.json
index 3418d30..958d240 100644
--- a/apps/web/messages/tr/common.json
+++ b/apps/web/messages/tr/common.json
@@ -16,5 +16,11 @@
"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."
+ },
+ "legal": {
+ "nav": "Yasal",
+ "legal": "Yasal bildirim",
+ "privacy": "Gizlilik bildirimi",
+ "terms": "Kullanım koşulları"
}
}
diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
index a419cbe..ce4e94a 100644
--- a/apps/web/next-env.d.ts
+++ b/apps/web/next-env.d.ts
@@ -1,7 +1,7 @@
///
///
-import "./.next/dev/types/routes.d.ts";
-import "./.next/dev/types/root-params.d.ts";
+import "./.next/types/routes.d.ts";
+import "./.next/types/root-params.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/web/src/app/u/layout.tsx b/apps/web/src/app/u/layout.tsx
index 82e0e56..e6fc55a 100644
--- a/apps/web/src/app/u/layout.tsx
+++ b/apps/web/src/app/u/layout.tsx
@@ -3,8 +3,20 @@ 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. */
+type Tree = Record>;
+
+/**
+ * Public profiles: the shared words and only the account words this page uses (the profile, the
+ * compare card, the level meter, the trophy grades and the shared parts), not the security and
+ * data texts of the signed-in area. Anyone can open this page, so it carries as little as it can.
+ * A key missing here shows as a missing message in the profile e2e test.
+ */
export default async function ProfileLayout({ children }: { children: ReactNode }) {
- const { common, account } = (await getMessages()) as Record>;
- return {children};
+ const { common, account } = (await getMessages()) as Tree;
+ const { publicProfile, compare, level, state, ui, trophies } = account as Tree;
+ return (
+
+ {children}
+
+ );
}
diff --git a/apps/web/src/components/auth/auth-shell.tsx b/apps/web/src/components/auth/auth-shell.tsx
index 15ed0b7..3cfdaa2 100644
--- a/apps/web/src/components/auth/auth-shell.tsx
+++ b/apps/web/src/components/auth/auth-shell.tsx
@@ -21,6 +21,7 @@ import { DitherField } from "../landing/shaders";
*/
export function AuthShell({ children, toasts = true }: { children: ReactNode; toasts?: boolean }) {
const t = useTranslations("auth.shell");
+ const legal = useTranslations("common.legal");
const { discord } = useSite();
return (
@@ -88,18 +89,18 @@ export function AuthShell({ children, toasts = true }: { children: ReactNode; to