mirror of
https://github.com/UiO-Gaming/uiogaming.no.git
synced 2026-11-05 13:56:47 +01:00
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:
22 files changed
+191
-150
No files matched your search
@@ -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"
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
File renamed without changes.
@@ -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}
|
||||
File renamed without changes.
@@ -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,6 +76,7 @@ const Event = async ({ params }: EventPageParams) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className={styles.container}>
|
||||
<Back />
|
||||
<div className={styles.event}>
|
||||
@@ -92,6 +92,7 @@ const Event = async ({ params }: EventPageParams) => {
|
||||
<p className={styles.description}>{data.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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({})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default function RootPage() {
|
||||
redirect("/en/404")
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
? [
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--link-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.button:hover,
|
||||
.button *:hover {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
})
|
||||
@@ -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*"],
|
||||
}
|
||||
Reference in new issue
Block a user