mirror of
https://github.com/KivotosRailway/website.git
synced 2026-11-03 11:06:32 +09:00
新增 SPA 支持
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import { useRouteLocale } from "@/components/layout/locale-context";
|
import { useRouteLocale } from "@/components/layout/locale-context";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { useEffect, useState, useSyncExternalStore } from "react";
|
import { useEffect, useState, useSyncExternalStore } from "react";
|
||||||
import { applyLocale, getInitialLocale, withLocale, getLocaleMessages, type Locale } from "@/lib/i18n";
|
import { applyLocale, getInitialLocale, withLocale, getLocaleMessages, type Locale } from "@/lib/i18n";
|
||||||
import { ThemeToggle } from "@/components/layout/theme-toggle";
|
import { ThemeToggle } from "@/components/layout/theme-toggle";
|
||||||
@@ -81,9 +82,12 @@ export function SiteFooter() {
|
|||||||
<div key={`column-${groupIndex}`} className="site-footer-column">
|
<div key={`column-${groupIndex}`} className="site-footer-column">
|
||||||
<h3>{group.title}</h3>
|
<h3>{group.title}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{group.links.map((link, linkIndex) => (
|
{group.links.map((link, linkIndex) => {
|
||||||
<li key={`column-${groupIndex}-link-${linkIndex}`}><a href={withLocale(footerColumnHrefs[groupIndex][linkIndex], locale)}>{link}</a></li>
|
const href = withLocale(footerColumnHrefs[groupIndex][linkIndex], locale);
|
||||||
))}
|
return <li key={`column-${groupIndex}-link-${linkIndex}`}>
|
||||||
|
{href.startsWith("/") ? <Link href={href}>{link}</Link> : <a href={href}>{link}</a>}
|
||||||
|
</li>;
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -104,9 +108,12 @@ export function SiteFooter() {
|
|||||||
|
|
||||||
<div className="site-footer-meta">
|
<div className="site-footer-meta">
|
||||||
<div className="site-footer-legal-row">
|
<div className="site-footer-legal-row">
|
||||||
{messages.footer.legal.map((item, itemIndex) => (
|
{messages.footer.legal.map((item, itemIndex) => {
|
||||||
<a key={`legal-${itemIndex}`} href={withLocale(footerLegalHrefs[itemIndex], locale)}>{item}</a>
|
const href = withLocale(footerLegalHrefs[itemIndex], locale);
|
||||||
))}
|
return href.startsWith("/")
|
||||||
|
? <Link key={`legal-${itemIndex}`} href={href}>{item}</Link>
|
||||||
|
: <a key={`legal-${itemIndex}`} href={href}>{item}</a>;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<p className="site-footer-description">{messages.footer.description}</p>
|
<p className="site-footer-description">{messages.footer.description}</p>
|
||||||
<div className="site-footer-copyright-row">
|
<div className="site-footer-copyright-row">
|
||||||
|
|||||||
@@ -4,12 +4,23 @@ import { useRouteLocale } from "@/components/layout/locale-context";
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { applyLocale, getInitialLocale, withLocale, withoutLocale, getLocaleMessages, localeLabels, locales, localeShortLabels, type Locale } from "@/lib/i18n";
|
import { applyLocale, getInitialLocale, withLocale, withoutLocale, getLocaleMessages, localeLabels, locales, localeShortLabels, type Locale } from "@/lib/i18n";
|
||||||
|
|
||||||
export function SiteHeader() {
|
export function SiteHeader() {
|
||||||
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
const locale = useRouteLocale();
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const changeLocale = (nextLocale: Locale) => {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem("kr-locale", nextLocale);
|
||||||
|
} catch { /* The URL remains the source of truth when storage is unavailable. */ }
|
||||||
|
|
||||||
|
const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||||
|
router.replace(withLocale(href, nextLocale));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateDocumentTitle = (nextLocale: Locale) => {
|
const updateDocumentTitle = (nextLocale: Locale) => {
|
||||||
@@ -25,10 +36,7 @@ export function SiteHeader() {
|
|||||||
document.title = pageLabel ? `${pageLabel} | ${messages.common.siteTitle}` : messages.common.siteTitle;
|
document.title = pageLabel ? `${pageLabel} | ${messages.common.siteTitle}` : messages.common.siteTitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
const syncLocale = (nextLocale: Locale) => {
|
const syncLocale = (nextLocale: Locale) => updateDocumentTitle(nextLocale);
|
||||||
setLocale(nextLocale);
|
|
||||||
updateDocumentTitle(nextLocale);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLocaleChange = (event: Event) => {
|
const handleLocaleChange = (event: Event) => {
|
||||||
const nextLocale = (event as CustomEvent<{ locale?: Locale }>).detail?.locale ?? getInitialLocale();
|
const nextLocale = (event as CustomEvent<{ locale?: Locale }>).detail?.locale ?? getInitialLocale();
|
||||||
@@ -83,8 +91,7 @@ export function SiteHeader() {
|
|||||||
className={item === locale ? "active" : ""}
|
className={item === locale ? "active" : ""}
|
||||||
aria-pressed={item === locale}
|
aria-pressed={item === locale}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLocale(item);
|
changeLocale(item);
|
||||||
applyLocale(item);
|
|
||||||
}}
|
}}
|
||||||
aria-label={localeLabels[item]}
|
aria-label={localeLabels[item]}
|
||||||
title={localeLabels[item]}
|
title={localeLabels[item]}
|
||||||
@@ -103,8 +110,7 @@ export function SiteHeader() {
|
|||||||
className={item === locale ? "active" : ""}
|
className={item === locale ? "active" : ""}
|
||||||
aria-pressed={item === locale}
|
aria-pressed={item === locale}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLocale(item);
|
changeLocale(item);
|
||||||
applyLocale(item);
|
|
||||||
}}
|
}}
|
||||||
aria-label={localeLabels[item]}
|
aria-label={localeLabels[item]}
|
||||||
title={localeLabels[item]}
|
title={localeLabels[item]}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useRouteLocale } from "@/components/layout/locale-context";
|
import { useRouteLocale } from "@/components/layout/locale-context";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { getInitialLocale, withLocale, type Locale } from "@/lib/i18n";
|
import { getInitialLocale, withLocale, type Locale } from "@/lib/i18n";
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ function parseLocalizedMessages(source: string): Partial<Record<Locale, string[]
|
|||||||
export function HttpStatusPage({ code }: { code: HttpStatusCode }) {
|
export function HttpStatusPage({ code }: { code: HttpStatusCode }) {
|
||||||
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
||||||
const [randomMessages, setRandomMessages] = useState<Partial<Record<Locale, string[]>>>({});
|
const [randomMessages, setRandomMessages] = useState<Partial<Record<Locale, string[]>>>({});
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const syncLocale = () => setLocale(getInitialLocale());
|
const syncLocale = () => setLocale(getInitialLocale());
|
||||||
@@ -105,7 +107,7 @@ export function HttpStatusPage({ code }: { code: HttpStatusCode }) {
|
|||||||
window.history.back();
|
window.history.back();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.location.assign(withLocale("/", locale));
|
router.replace(withLocale("/", locale));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user