mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 16:12:02 +00:00
fix: show a translated 404 to a signed-in user who mistypes a URL instead of the login page
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
// Unknown paths below a locale (/de/typo) end here, so the 404 renders
|
||||
// inside the locale layout and is translated ([locale]/not-found.tsx).
|
||||
// Removed from the static Lite export, like (main)/[...rest].
|
||||
export default function LocaleCatchAll() {
|
||||
notFound();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { NotFoundView } from "@/components/error/not-found-view";
|
||||
|
||||
export default function LocaleNotFound() {
|
||||
const t = useTranslations("not_found");
|
||||
const tErrors = useTranslations("errors");
|
||||
return <NotFoundView title={t("title")} description={t("description")} homeLabel={tErrors("go_home")} />;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { getPathPrefix } from "@/lib/browser-navigation";
|
||||
import { NotFoundView } from "@/components/error/not-found-view";
|
||||
import { IS_LITE } from "@/lib/lite";
|
||||
import { liteSurfaceRootFor, stashPendingLitePath } from "@/lib/lite-link-segments";
|
||||
|
||||
// Outside the locale tree there are no translations: paths below a locale
|
||||
// get the translated page in [locale]/not-found.tsx.
|
||||
export default function NotFound() {
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
|
||||
useEffect(() => {
|
||||
// Static Lite build on a host without rewrite rules (GitHub Pages): a deep
|
||||
// link such as /en/mail/thread/abc lands here as 404.html. Park the link
|
||||
@@ -21,42 +21,13 @@ export default function NotFound() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!isAuthenticated) {
|
||||
const prefix = getPathPrefix();
|
||||
// Don't redirect admin routes to the webmail login page. Admin paths
|
||||
// are mounted relative to the deployment prefix, so account for it.
|
||||
const adminBase = `${prefix}/admin`;
|
||||
const isAdminRoute = window.location.pathname === adminBase || window.location.pathname.startsWith(`${adminBase}/`);
|
||||
if (!isAdminRoute) {
|
||||
window.location.href = `${prefix}/login`;
|
||||
}
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
let isAdmin = false;
|
||||
if (typeof window !== 'undefined') {
|
||||
const prefix = getPathPrefix();
|
||||
const adminBase = `${prefix}/admin`;
|
||||
isAdmin = window.location.pathname === adminBase || window.location.pathname.startsWith(`${adminBase}/`);
|
||||
}
|
||||
if (!isAdmin) return null;
|
||||
}
|
||||
|
||||
const prefix = typeof window !== 'undefined' ? getPathPrefix() : '';
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center max-w-md px-4">
|
||||
<h1 className="text-4xl font-bold text-foreground mb-2">404</h1>
|
||||
<p className="text-muted-foreground mb-6">This page could not be found.</p>
|
||||
<a
|
||||
href={`${prefix}/`}
|
||||
className="inline-flex items-center px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Go home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<NotFoundView
|
||||
title="Page not found"
|
||||
description="This page doesn't exist or has moved. Check the address, or go back to your inbox."
|
||||
homeLabel="Go to inbox"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// A mistyped URL with a still valid session: the 404 waits for the session
|
||||
// to be restored instead of sending the user to the login page.
|
||||
|
||||
const { default: LocaleNotFound } = await import('@/app/(main)/[locale]/not-found');
|
||||
const { useAuthStore } = await import('@/stores/auth-store');
|
||||
|
||||
const realLocation = window.location;
|
||||
let href = '';
|
||||
|
||||
beforeEach(() => {
|
||||
href = 'http://localhost/de/gibtsnicht';
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: {
|
||||
...realLocation,
|
||||
pathname: '/de/gibtsnicht',
|
||||
get href() { return href; },
|
||||
set href(v: string) { href = v; },
|
||||
},
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
Object.defineProperty(window, 'location', { configurable: true, value: realLocation });
|
||||
});
|
||||
|
||||
describe('404 page', () => {
|
||||
it('restores the session before deciding, and shows the translated page to a signed-in user', async () => {
|
||||
let finish: () => void = () => {};
|
||||
const checkAuth = vi.fn(() => new Promise<void>((resolve) => {
|
||||
finish = () => { useAuthStore.setState({ isAuthenticated: true } as never); resolve(); };
|
||||
}));
|
||||
useAuthStore.setState({ isAuthenticated: false, checkAuth } as never);
|
||||
|
||||
render(<LocaleNotFound />);
|
||||
expect(checkAuth).toHaveBeenCalled();
|
||||
expect(href).not.toContain('/login');
|
||||
|
||||
await act(async () => { finish(); });
|
||||
expect(href).not.toContain('/login');
|
||||
expect(screen.getByRole('heading', { name: 'title' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'go_home' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('sends a visitor without a session to the login page once that is known', async () => {
|
||||
const checkAuth = vi.fn(() => Promise.resolve());
|
||||
useAuthStore.setState({ isAuthenticated: false, checkAuth } as never);
|
||||
render(<LocaleNotFound />);
|
||||
await act(async () => {});
|
||||
expect(href).toBe('/login');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { SearchX } from "@/components/icons";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { getPathPrefix } from "@/lib/browser-navigation";
|
||||
|
||||
/** Whether the current URL is an admin page, which has its own sign-in. */
|
||||
function isAdminPath(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
const adminBase = `${getPathPrefix()}/admin`;
|
||||
const path = window.location.pathname;
|
||||
return path === adminBase || path.startsWith(`${adminBase}/`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Who is looking at a missing page: "checking" until the stored session has
|
||||
* been restored, then signed in or not. Deciding before that sent a signed-in
|
||||
* user with a mistyped URL to the login page.
|
||||
*/
|
||||
export function useNotFoundAuth(): "checking" | "signed-in" | "signed-out" {
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
const [checked, setChecked] = useState(() => useAuthStore.getState().isAuthenticated);
|
||||
|
||||
useEffect(() => {
|
||||
if (useAuthStore.getState().isAuthenticated) {
|
||||
setChecked(true);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void useAuthStore.getState().checkAuth().finally(() => { if (!cancelled) setChecked(true); });
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!checked || isAuthenticated || isAdminPath()) return;
|
||||
window.location.href = `${getPathPrefix()}/login`;
|
||||
}, [checked, isAuthenticated]);
|
||||
|
||||
if (isAuthenticated) return "signed-in";
|
||||
return checked ? "signed-out" : "checking";
|
||||
}
|
||||
|
||||
export interface NotFoundViewProps {
|
||||
title: string;
|
||||
description: string;
|
||||
homeLabel: string;
|
||||
}
|
||||
|
||||
/** The 404 page: what happened and the way back. Signed out, it shows only on admin paths. */
|
||||
export function NotFoundView({ title, description, homeLabel }: NotFoundViewProps) {
|
||||
const auth = useNotFoundAuth();
|
||||
if (auth !== "signed-in" && !isAdminPath()) return null;
|
||||
|
||||
const prefix = typeof window !== "undefined" ? getPathPrefix() : "";
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center max-w-md px-4">
|
||||
<SearchX className="w-12 h-12 mx-auto mb-4 text-muted-foreground" aria-hidden="true" />
|
||||
<h1 className="text-xl font-semibold text-foreground mb-2">{title}</h1>
|
||||
<p className="text-muted-foreground mb-6">{description}</p>
|
||||
<a
|
||||
href={`${prefix}/`}
|
||||
className="inline-flex items-center justify-center h-9 px-3 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
{homeLabel}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "إعادة المحاولة",
|
||||
"go_home": "الانتقال إلى البريد الوارد"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "الصفحة غير موجودة",
|
||||
"description": "هذه الصفحة غير موجودة أو نُقلت. تحقّق من العنوان أو عُد إلى صندوق الوارد."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "رد",
|
||||
"reply_all": "الرد على الجميع",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Reintenta",
|
||||
"go_home": "Vés a la safata d'entrada"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "No s'ha trobat la pàgina",
|
||||
"description": "Aquesta pàgina no existeix o s'ha mogut. Comproveu l'adreça o torneu a la safata d'entrada."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Respon",
|
||||
"reply_all": "Respon a tots",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Opakovat",
|
||||
"go_home": "Přejít do doručené pošty"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Stránka nenalezena",
|
||||
"description": "Tato stránka neexistuje nebo byla přesunuta. Zkontrolujte adresu nebo se vraťte do doručené pošty."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Odpovědět",
|
||||
"reply_all": "Odpovědět všem",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Prøv igen",
|
||||
"go_home": "Gå til indbakke"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Siden blev ikke fundet",
|
||||
"description": "Siden findes ikke eller er flyttet. Tjek adressen, eller gå tilbage til din indbakke."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Svar",
|
||||
"reply_all": "Svar alle",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Wiederholen",
|
||||
"go_home": "Zum Posteingang"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Seite nicht gefunden",
|
||||
"description": "Diese Seite gibt es nicht oder sie wurde verschoben. Prüfen Sie die Adresse oder kehren Sie zu Ihrem Posteingang zurück."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Antworten",
|
||||
"reply_all": "Allen antworten",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Retry",
|
||||
"go_home": "Go to inbox"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Page not found",
|
||||
"description": "This page doesn't exist or has moved. Check the address, or go back to your inbox."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Reply",
|
||||
"reply_all": "Reply All",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Reintentar",
|
||||
"go_home": "Ir a bandeja de entrada"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "No se ha encontrado la página",
|
||||
"description": "Esta página no existe o se ha movido. Comprueba la dirección o vuelve a tu bandeja de entrada."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Responder",
|
||||
"reply_all": "Responder a Todos",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Proovi uuesti",
|
||||
"go_home": "Mine postkasti"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Lehte ei leitud",
|
||||
"description": "Seda lehte pole olemas või see on teisaldatud. Kontrollige aadressi või minge tagasi postkasti."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Vasta",
|
||||
"reply_all": "Vasta kõigile",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "تلاش مجدد",
|
||||
"go_home": "رفتن به صندوق ورودی"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "صفحه پیدا نشد",
|
||||
"description": "این صفحه وجود ندارد یا جابهجا شده است. نشانی را بررسی کنید یا به صندوق ورودی برگردید."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "پاسخ",
|
||||
"reply_all": "پاسخ به همه",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Réessayer",
|
||||
"go_home": "Aller à la boîte de réception"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Page introuvable",
|
||||
"description": "Cette page n'existe pas ou a été déplacée. Vérifiez l'adresse ou revenez à votre boîte de réception."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Répondre",
|
||||
"reply_all": "Répondre à tous",
|
||||
|
||||
@@ -2591,6 +2591,10 @@
|
||||
"retry": "נסה שוב",
|
||||
"go_home": "עבור לתיבת הדואר הנכנס"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "הדף לא נמצא",
|
||||
"description": "הדף הזה לא קיים או שהועבר. בדקו את הכתובת או חזרו לתיבת הדואר הנכנס."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "השב",
|
||||
"reply_all": "השב לכולם",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Újra",
|
||||
"go_home": "Ugrás a beérkezőhöz"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Az oldal nem található",
|
||||
"description": "Ez az oldal nem létezik, vagy áthelyezték. Ellenőrizze a címet, vagy térjen vissza a beérkezett üzenetekhez."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Válasz",
|
||||
"reply_all": "Válasz mindenkinek",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Riprova",
|
||||
"go_home": "Vai alla posta in arrivo"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Pagina non trovata",
|
||||
"description": "Questa pagina non esiste o è stata spostata. Controlla l'indirizzo o torna alla posta in arrivo."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Rispondi",
|
||||
"reply_all": "Rispondi a tutti",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "リトライ",
|
||||
"go_home": "受信トレイに移動"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "ページが見つかりません",
|
||||
"description": "このページは存在しないか、移動されました。アドレスを確認するか、受信トレイに戻ってください。"
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "返信",
|
||||
"reply_all": "全員に返信",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "재시도",
|
||||
"go_home": "받은편지함으로 이동"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "페이지를 찾을 수 없습니다",
|
||||
"description": "이 페이지가 없거나 이동되었습니다. 주소를 확인하거나 받은편지함으로 돌아가세요."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "답장",
|
||||
"reply_all": "전체 답장",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Mēģināt vēlreiz",
|
||||
"go_home": "Uz iesūtni"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Lapa nav atrasta",
|
||||
"description": "Šī lapa nepastāv vai ir pārvietota. Pārbaudiet adresi vai atgriezieties iesūtnē."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Atbildēt",
|
||||
"reply_all": "Atbildēt visiem",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Дахин оролдох",
|
||||
"go_home": "Ирсэн имэйл рүү очих"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Хуудас олдсонгүй",
|
||||
"description": "Энэ хуудас байхгүй эсвэл зөөгдсөн байна. Хаягаа шалгах эсвэл ирсэн захидал руу буцна уу."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Хариулах",
|
||||
"reply_all": "Бүгдэд нь хариулах",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Prøv på nytt",
|
||||
"go_home": "Gå til innboksen"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Fant ikke siden",
|
||||
"description": "Denne siden finnes ikke eller er flyttet. Sjekk adressen, eller gå tilbake til innboksen."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Svar",
|
||||
"reply_all": "Svar alle",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Opnieuw proberen",
|
||||
"go_home": "Ga naar postvak IN"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Pagina niet gevonden",
|
||||
"description": "Deze pagina bestaat niet of is verplaatst. Controleer het adres of ga terug naar je inbox."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Beantwoorden",
|
||||
"reply_all": "Allen beantwoorden",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Ponów",
|
||||
"go_home": "Przejdź do skrzynki odbiorczej"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Nie znaleziono strony",
|
||||
"description": "Ta strona nie istnieje lub została przeniesiona. Sprawdź adres albo wróć do skrzynki odbiorczej."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Odpowiedz",
|
||||
"reply_all": "Odpowiedz wszystkim",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Tentar novamente",
|
||||
"go_home": "Ir para caixa de entrada"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Página não encontrada",
|
||||
"description": "Esta página não existe ou foi movida. Verifique o endereço ou volte à caixa de entrada."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Responder",
|
||||
"reply_all": "Responder a Todos",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Reîncearcă",
|
||||
"go_home": "Accesați căsuța de intrare"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Pagina nu a fost găsită",
|
||||
"description": "Această pagină nu există sau a fost mutată. Verificați adresa sau reveniți la Inbox."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Răspunde",
|
||||
"reply_all": "Răspunde tuturor",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Повторить",
|
||||
"go_home": "В папку «Входящие»"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Страница не найдена",
|
||||
"description": "Этой страницы нет или она перемещена. Проверьте адрес или вернитесь во «Входящие»."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Ответить",
|
||||
"reply_all": "Ответить всем",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Opakovať",
|
||||
"go_home": "Prejsť do doručenej pošty"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Stránka sa nenašla",
|
||||
"description": "Táto stránka neexistuje alebo bola presunutá. Skontrolujte adresu alebo sa vráťte do doručenej pošty."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Odpovedať",
|
||||
"reply_all": "Odpovedať všetkým",
|
||||
|
||||
@@ -101,6 +101,10 @@
|
||||
"retry": "ลองใหม่",
|
||||
"go_home": "ไปยังกล่องข้อความ"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "ไม่พบหน้านี้",
|
||||
"description": "ไม่มีหน้านี้หรือหน้านี้ถูกย้ายแล้ว ตรวจสอบที่อยู่ หรือกลับไปที่กล่องจดหมายเข้า"
|
||||
},
|
||||
"login": {
|
||||
"title": "เข้าสู่ระบบจดหมายอิเล็กทรอนิกส์",
|
||||
"username_label": "อีเมล",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Yeniden dene",
|
||||
"go_home": "Gelen kutusuna git"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Sayfa bulunamadı",
|
||||
"description": "Bu sayfa yok veya taşınmış. Adresi kontrol edin ya da gelen kutunuza dönün."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Yanıtla",
|
||||
"reply_all": "Tümünü Yanıtla",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "Повторіть спробу",
|
||||
"go_home": "Перейти до папки \"Вхідні\"."
|
||||
},
|
||||
"not_found": {
|
||||
"title": "Сторінку не знайдено",
|
||||
"description": "Цієї сторінки немає або її переміщено. Перевірте адресу або поверніться до вхідних."
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "Відповісти",
|
||||
"reply_all": "Відповісти всім",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "重試",
|
||||
"go_home": "前往收件匣"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "找不到頁面",
|
||||
"description": "此頁面不存在或已移動。請檢查網址,或返回收件匣。"
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "回覆",
|
||||
"reply_all": "全部回覆",
|
||||
|
||||
@@ -2625,6 +2625,10 @@
|
||||
"retry": "重试",
|
||||
"go_home": "前往收件箱"
|
||||
},
|
||||
"not_found": {
|
||||
"title": "找不到页面",
|
||||
"description": "此页面不存在或已移动。请检查地址,或返回收件箱。"
|
||||
},
|
||||
"context_menu": {
|
||||
"reply": "回复",
|
||||
"reply_all": "全部回复",
|
||||
|
||||
@@ -39,6 +39,7 @@ export const LITE_REMOVED_PATHS = [
|
||||
"instrumentation.ts",
|
||||
"instrumentation.node.ts",
|
||||
"app/(main)/[...rest]",
|
||||
"app/(main)/[locale]/[...rest]",
|
||||
"app/(main)/protocol",
|
||||
"e2e",
|
||||
"integration",
|
||||
|
||||
Reference in New Issue
Block a user