Merge pull request #51 from UiO-Gaming/fix/translation-rewrite

fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple fuck apple
This commit is contained in:
leander authored and GitHub committed 2025-09-16 04:09:47 +02:00
commit 2591eacd14
22 files changed
+204 -163

No files matched your search

+5
View File
@@ -6,6 +6,11 @@
"complaint": "Complaint",
"documents": "Documents"
},
"metaTags": {
"time": "Time",
"location": "Location",
"description": "A student organisation that aims to create an inclusive environment around gaming and e-sports for the students at UiO"
},
"buttons": {
"back": "back",
"flagImageAlt": "Norwegian flag"
+5
View File
@@ -6,6 +6,11 @@
"complaint": "Klage",
"documents": "Dokumenter"
},
"metaTags": {
"time": "Tid",
"location": "Sted",
"description": "En hobbyforening som ønsker skape et sosialt miljø rundt gaming og e-sport på UiO!"
},
"buttons": {
"back": "tilbake",
"flagImageAlt": "Britisk flagg"
+1 -1
View File
@@ -1,7 +1,7 @@
import type { NextConfig } from "next"
import createNextIntlPlugin from "next-intl/plugin"
const withNextIntl = createNextIntlPlugin()
const withNextIntl = createNextIntlPlugin("./src/i18n.ts")
const nextConfig: NextConfig = {
images: {
@@ -1,4 +1,4 @@
import Layout from "@/app/layout"
import Layout from "@/app/[locale]/layout"
import Footer from "@/components/page-sections/footer"
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
@@ -9,6 +9,7 @@ import { getBlogPost } from "@/lib/sanity"
import { PortableText, PortableTextComponents } from "@portabletext/react"
import { Metadata, Viewport } from "next"
import { notFound } from "next/navigation"
import { getLocale } from "next-intl/server"
import Image from "next/image"
@@ -17,6 +18,7 @@ import * as styles from "./page.module.css"
interface BlogPageParams {
params: Promise<{
slug: string
locale: string
}>
}
@@ -39,12 +41,6 @@ export async function generateMetadata({
const { slug } = await params
const data = await getBlogPost(slug)
if (!data) {
return {
title: "Blog Post Not Found | UiO Gaming",
}
}
return generateSeoMetadata({
title: data.title,
description: data.excerpt,
@@ -103,9 +99,7 @@ const BlogPost = async ({ params }: BlogPageParams) => {
const data = await getBlogPost(slug)
if (!data) {
throw new Error("Blog post not found")
}
if (!data) return notFound()
const createdAt = new Date(data._createdAt).toLocaleDateString(locale, {
day: "numeric",
@@ -136,7 +130,7 @@ const BlogPost = async ({ params }: BlogPageParams) => {
{data.author?.image && (
<Image
className="circular"
alt={`Forfatteren av innlegget, ${data.author.name}`}
alt={data.author.name}
src={urlFor(data.author.image).width(50).height(50).url()}
width={50}
height={50}
@@ -1,10 +1,13 @@
import Layout from "@/app/[locale]/layout"
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
import Back from "@/components/ui/back"
import { getEvent } from "@/lib/sanity"
import { Metadata, Viewport } from "next"
import { getLocale } from "next-intl/server"
import { notFound } from "next/navigation"
import { getLocale, getTranslations } from "next-intl/server"
import { FaCalendarAlt, FaMapMarkerAlt } from "react-icons/fa"
import * as styles from "./page.module.css"
@@ -12,6 +15,7 @@ import * as styles from "./page.module.css"
interface EventPageParams {
params: Promise<{
slug: string
locale: string
}>
}
@@ -21,11 +25,8 @@ export async function generateMetadata({
const { slug } = await params
const data = await getEvent(slug)
if (!data) {
return {
title: "Event Not Found | UiO Gaming",
}
}
const locale = await getLocale()
const t = await getTranslations("metaTags")
const formatDate = (dateString: string) => {
const date = new Date(dateString)
@@ -38,12 +39,12 @@ export async function generateMetadata({
minute: "2-digit",
timeZone: "Europe/Oslo",
}
return date.toLocaleString("no", options)
return date.toLocaleString(locale, options)
}
return generateSeoMetadata({
title: data.title,
description: `Tid: ${formatDate(data.date)}\nSted: ${data.location}\n\n${data.description}`,
description: `${t("time")}: ${formatDate(data.date)}\n${t("location")}: ${data.location}\n\n${data.description}`,
})
}
@@ -57,9 +58,7 @@ const Event = async ({ params }: EventPageParams) => {
const data = await getEvent(slug)
if (!data) {
throw new Error("Event not found")
}
if (!data) return notFound()
const formatDate = (dateString: string) => {
const date = new Date(dateString)
@@ -77,21 +76,23 @@ const Event = async ({ params }: EventPageParams) => {
}
return (
<div className={styles.container}>
<Back />
<div className={styles.event}>
<h1>{data.title}</h1>
<div className={styles.metadata}>
<FaMapMarkerAlt />
<p>{data.location}</p>
<Layout>
<div className={styles.container}>
<Back />
<div className={styles.event}>
<h1>{data.title}</h1>
<div className={styles.metadata}>
<FaMapMarkerAlt />
<p>{data.location}</p>
</div>
<div className={styles.metadata}>
<FaCalendarAlt />
<p>{formatDate(data.date)}</p>
</div>
<p className={styles.description}>{data.description}</p>
</div>
<div className={styles.metadata}>
<FaCalendarAlt />
<p>{formatDate(data.date)}</p>
</div>
<p className={styles.description}>{data.description}</p>
</div>
</div>
</Layout>
)
}
+32
View File
@@ -0,0 +1,32 @@
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from "next-intl"
import { getLocale } from "next-intl/server"
import LanguageSelector from "@components/ui/languageSelector"
import UmamiAnalytics from "@/components/umamiAnalytics"
import { getAnalyticsConfig } from "@/lib/analyticsConfig"
export default async function LocaleLayout({
children,
}: {
children: React.ReactNode
}) {
const locale = (await getLocale()) as "no" | "en"
const messages = (await import(`../../../messages/${locale}.json`)).default
const analyticsConfig = getAnalyticsConfig()
return (
<NextIntlClientProvider locale={locale} messages={messages}>
<LanguageSelector />
<main>{children}</main>
{analyticsConfig?.enabled && (
<UmamiAnalytics
websiteId={analyticsConfig.websiteId}
src={analyticsConfig.src}
domains={analyticsConfig.domains}
/>
)}
</NextIntlClientProvider>
)
}
@@ -1,4 +1,4 @@
import Layout from "@/app/layout"
import Layout from "@/app/[locale]/layout"
import About from "@/components/page-sections/about"
import BoardMembers from "@/components/page-sections/boardMembers"
@@ -13,7 +13,7 @@ import { Metadata, Viewport } from "next"
export const revalidate = 3600 // 1 hour
export function generateMetadata(): Metadata {
export async function generateMetadata(): Promise<Metadata> {
return generateSeoMetadata({})
}
+5 -2
View File
@@ -3,14 +3,17 @@ import Layout from "@/app/layout"
import { generateSeoMetadata, generateSeoViewport } from "@/components/seo"
import { Metadata, Viewport } from "next"
import { getTranslations } from "next-intl/server"
import { useTranslations } from "next-intl"
import Link from "next/link"
import * as styles from "./global-not-found.module.css"
export function generateMetadata(): Metadata {
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("404")
return generateSeoMetadata({
title: "404: Ikke funnet",
title: t("title"),
description: "Uh oh :(",
})
}
+5 -27
View File
@@ -1,12 +1,6 @@
import "@/app/globals.css"
import { LocaleProvider } from "@/components/localeProvider"
import UmamiAnalytics from "@/components/umamiAnalytics"
import { getAnalyticsConfig } from "@/lib/analyticsConfig"
import type { Metadata } from "next"
import { getLocale } from "next-intl/server"
export const metadata: Metadata = {
title: "UiO Gaming",
@@ -19,30 +13,14 @@ export const metadata: Metadata = {
},
}
export default async function RootLayout({
export default function RootLayout({
children,
}: Readonly<{
}: {
children: React.ReactNode
}>) {
const locale = (await getLocale()) as "no" | "en"
const messages = (await import(`../../messages/${locale}.json`)).default // TODO: duplicate of request.ts?
const analyticsConfig = getAnalyticsConfig()
}) {
return (
<html lang={locale}>
<body>
<LocaleProvider locale={locale} messages={messages}>
<main>{children}</main>
{analyticsConfig?.enabled && (
<UmamiAnalytics
websiteId={analyticsConfig.websiteId}
src={analyticsConfig.src}
domains={analyticsConfig.domains}
/>
)}
</LocaleProvider>
</body>
<html>
<body>{children}</body>
</html>
)
}
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation"
export default function RootPage() {
redirect("/en/404")
}
-24
View File
@@ -1,24 +0,0 @@
.button {
display: flex;
gap: 0.25rem;
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 0;
background: pink;
color: black;
border: 1px solid black;
font-size: 1.1rem;
border-radius: 0.5rem;
padding-inline: 0.5em;
}
.button:hover {
background: #ff7b7b;
color: white;
}
.flag {
width: 1.2rem;
}
-63
View File
@@ -1,63 +0,0 @@
"use client"
import { GB, NO } from "country-flag-icons/react/3x2"
import { NextIntlClientProvider } from "next-intl"
import { ReactNode, useEffect, useState } from "react"
import * as styles from "./localeProvider.module.css"
type LocaleProviderProps = {
locale: "no" | "en"
messages: Record<string, string>
children: ReactNode
}
export function LocaleProvider({
locale: serverLocale,
messages,
children,
}: LocaleProviderProps) {
const [locale, setLocale] = useState<"no" | "en">(serverLocale)
const [loadedMessages, setLoadedMessages] =
useState<Record<string, string>>(messages)
useEffect(() => {
const match = document.cookie.match(/(?:^|; )locale=([^;]+)/)
const saved = match ? decodeURIComponent(match[1]) : null
if (saved && saved !== locale) {
setLocale(saved as "no" | "en")
import(`../../messages/${saved}.json`).then(module => {
setLoadedMessages(module.default)
})
}
}, [locale])
const changeLocale = (newLocale: "no" | "en") => {
setLocale(newLocale)
document.cookie = `locale=${encodeURIComponent(
newLocale
)}; path=/; max-age=31536000; SameSite=Lax`
window.location.reload()
}
return (
<NextIntlClientProvider locale={locale} messages={loadedMessages}>
{children}
<div className={styles.languageSwitcher}>
{locale === "no" ? (
<button onClick={() => changeLocale("en")} className={styles.button}>
<GB className={styles.flag} title="English flag" />
English
</button>
) : (
<button onClick={() => changeLocale("no")} className={styles.button}>
<NO className={styles.flag} title="Norwegian flag" />
Norsk
</button>
)}
</div>
</NextIntlClientProvider>
)
}
+1 -1
View File
@@ -50,7 +50,7 @@ const Events = async () => {
const mapEvents = (events: Event[]) => {
if (events.length !== 0) {
return events.map((event: Event) => (
<Link key={event._id} href={"/event/" + event.slug.current}>
<Link key={event._id} href={`/${locale}/event/${event.slug.current}`}>
<article className={styles.card}>
<h3>{event.title}</h3>
<div>
+13 -6
View File
@@ -1,4 +1,5 @@
import { Metadata, Viewport } from "next"
import { getLocale, getTranslations } from "next-intl/server"
type SeoProps = {
title?: string
@@ -7,16 +8,17 @@ type SeoProps = {
author?: string
}
export function generateSeoMetadata({
export async function generateSeoMetadata({
title,
description,
imageURL,
author,
}: SeoProps): Metadata {
}: SeoProps): Promise<Metadata> {
const t = await getTranslations("metaTags")
const locale = await getLocale()
const defaultTitle = "UiO Gaming"
const metaDescription =
description ||
"En hobbyforening som ønsker skape et sosialt miljö rundt gaming og e-sport på UiO!"
const metaDescription = description || t("description")
const siteUrl = "https://uiogaming.no"
const themeColor = "#7d36e7"
@@ -24,11 +26,16 @@ export function generateSeoMetadata({
title: title ? `${title} | ${defaultTitle}` : defaultTitle,
description: metaDescription,
authors: author ? [{ name: author }] : [{ name: "UiO Gaming" }],
icons: {
icon: "/logo_2024_neon_pink.png",
shortcut: "/logo_2024_neon_pink.png",
apple: "/logo_2024_neon_pink.png",
},
openGraph: {
title: title || defaultTitle,
description: metaDescription,
type: "website",
locale: "no",
locale: locale,
siteName: defaultTitle,
images: imageURL
? [
+1
View File
@@ -9,6 +9,7 @@
background: none;
border: none;
color: var(--link-color);
cursor: pointer;
}
.button:hover,
.button *:hover {
+5 -2
View File
@@ -1,11 +1,14 @@
"use client"
import { useTranslations } from "next-intl"
import { useLocale, useTranslations } from "next-intl"
import { useRouter } from "next/navigation"
import { FaArrowLeft } from "react-icons/fa"
import * as styles from "./back.module.css"
const Back = () => {
const locale = useLocale()
const router = useRouter()
const t = useTranslations("buttons")
const handleBack = () => {
@@ -18,7 +21,7 @@ const Back = () => {
if (window.history.length > 1 && isSameDomain) {
window.history.back()
} else {
window.location.href = "/"
router.push(`/${locale}`)
}
}
@@ -0,0 +1,30 @@
.languageSwitcher {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(0, 0, 0, 0.35);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.button:hover {
background: rgba(0, 0, 0, 1);
}
.flag {
width: 20px;
height: 15px;
border-radius: 2px;
}
+37
View File
@@ -0,0 +1,37 @@
"use client"
import { GB, NO } from "country-flag-icons/react/3x2"
import { useLocale } from "next-intl"
import { useRouter, usePathname } from "next/navigation"
import * as styles from "./languageSelector.module.css"
const LanguageSelector = () => {
const locale = useLocale()
const router = useRouter()
const pathname = usePathname()
const changeLocale = (newLocale: "no" | "en") => {
// Remove the current locale from the pathname
const pathWithoutLocale = pathname.replace(/^\/[a-z]{2}/, "")
router.push(`/${newLocale}${pathWithoutLocale}`)
}
return (
<div className={styles.languageSwitcher}>
{locale === "no" ? (
<button onClick={() => changeLocale("en")} className={styles.button}>
<GB className={styles.flag} title="English flag" />
English
</button>
) : (
<button onClick={() => changeLocale("no")} className={styles.button}>
<NO className={styles.flag} title="Norwegian flag" />
Norsk
</button>
)}
</div>
)
}
export default LanguageSelector
+15
View File
@@ -0,0 +1,15 @@
import { notFound } from "next/navigation"
import { getRequestConfig } from "next-intl/server"
const locales = ["no", "en"]
export default getRequestConfig(async ({ requestLocale }) => {
const locale = await requestLocale
if (!locale || !locales.includes(locale as "no" | "en")) notFound()
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
}
})
+12
View File
@@ -0,0 +1,12 @@
import createMiddleware from "next-intl/middleware"
export default createMiddleware({
locales: ["no", "en"],
defaultLocale: "no",
localePrefix: "always",
})
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(no|en)/:path*"],
}