From 50bdf6503e42b56834170625dbd39e248978a1f9 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Wed, 6 Aug 2025 01:16:40 +0200 Subject: [PATCH] reintroduce translation support --- messages/en.json | 53 +++++++ messages/no.json | 53 +++++++ next.config.ts | 5 +- package-lock.json | 144 +++++++++++++++++- package.json | 2 + src/app/blog/[slug]/page.tsx | 4 +- src/app/event/[slug]/page.tsx | 4 +- src/app/global-not-found.tsx | 9 +- src/app/layout.tsx | 27 ++-- src/components/localeProvider.module.css | 24 +++ src/components/localeProvider.tsx | 63 ++++++++ src/components/page-sections/about.tsx | 25 ++- src/components/page-sections/boardMembers.tsx | 14 +- src/components/page-sections/contact.tsx | 8 +- src/components/page-sections/events.tsx | 26 +++- src/components/page-sections/footer.tsx | 24 ++- src/components/page-sections/hero.tsx | 8 +- src/components/page-sections/office.tsx | 16 +- src/components/ui/back.tsx | 5 +- src/components/ui/memberButton.tsx | 6 +- src/components/ui/otherButton.tsx | 21 ++- src/i18n/request.ts | 12 ++ 22 files changed, 482 insertions(+), 71 deletions(-) create mode 100644 messages/en.json create mode 100644 messages/no.json create mode 100644 src/components/localeProvider.module.css create mode 100644 src/components/localeProvider.tsx create mode 100644 src/i18n/request.ts diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 0000000..42f8af9 --- /dev/null +++ b/messages/en.json @@ -0,0 +1,53 @@ +{ + "common": { + "photo": "Photo", + "join": "Become a member", + "statutes": "Statutes", + "complaint": "Complaint", + "documents": "Documents" + }, + "buttons": { + "back": "back", + "flagImageAlt": "Norwegian flag" + }, + "404": { + "title": "404: Page not found", + "text": "Our journey ended here. Please don't leave us :(", + "back": "Back to safe grounds" + }, + "hero": { + "text": "A student organisation that aims to create an inclusive environment around gaming and e-sports for the students at UiO" + }, + "about": { + "title": "Who are we?", + "imageAlt": "2 members playing together", + "text": "UiO Gaming is a student organisation with a primary focus on creating an inclusive environment around gaming and e-sports. We wish to establish a community where it’s easy to make new friends and cultivate your gaming interests. Each week we host both casual and more intense gamenights, as well as physical events such as board game nights and other social gatherings. Our main channel is a Discord server where you can meet new people with common interests and play games with other students.", + "interested": "Interested?", + "other": "Other" + }, + "events": { + "title": "Upcoming Events", + "imageAlt": "Members playing board games", + "readMore": "Read more", + "noEvents": "No upcoming events" + }, + "office": { + "title": "The Office", + "text": "We have an office at Chateau Neuf where you can use our gear and hang out with other members! We've got all the latest and greatest consoles, 6 high-end PCs as well as a plethora of board games.", + "tip": "Tip: You can look around the office by clicking and dragging the image below 👀" + }, + "boardMembers": { + "title": "The Board", + "imageAlt": "Potrait of board member", + "noContact": "No contact info" + }, + "contact": { + "title": "Find us", + "email": "E-mail" + }, + "footer": { + "sourceCode": "Source code", + "admin": "Admin page", + "orgNum": "Org. No." + } +} diff --git a/messages/no.json b/messages/no.json new file mode 100644 index 0000000..10b6f56 --- /dev/null +++ b/messages/no.json @@ -0,0 +1,53 @@ +{ + "common": { + "photo": "Foto", + "join": "Bli medlem", + "statutes": "Vedtekter", + "complaint": "Klage", + "documents": "Dokumenter" + }, + "buttons": { + "back": "tilbake", + "flagImageAlt": "Britisk flagg" + }, + "404": { + "title": "404: Siden finnes ikke", + "text": "Du har nådd en blindvei, men ikke på Blindern", + "back": "Tilbake til hjemmesiden" + }, + "hero": { + "text": "En hobbyforening som ønsker å skape et sosialt miljø rundt gaming og e-sport på UiO" + }, + "about": { + "title": "Hvem er vi?", + "imageAlt": "2 medlemmer som spiller sammen", + "text": "UiO Gaming er en forening med hovedfokus på å danne et inkluderende fellesskap rundt videospill og e-sport. Hos oss er det enkelt å skaffe seg venner, ettersom alle har felles interesser. Vi arrangerer ukentlige spillkvelder og fysiske sosiale arrangementer, som for eksempel brettspillkvelder og sosiale sammenkomster. Vår hovedkanal er en aktiv Discord-server hvor man kan møte nye folk med felles interesser og game med andre studenter.", + "interested": "Interessert?", + "other": "Annet" + }, + "events": { + "title": "Hva skjer?", + "imageAlt": "Medlemmer som spiller brettspill", + "readMore": "Les mer", + "noEvents": "Ingen kommende arrangementer" + }, + "office": { + "title": "Kontoret", + "text": "Vi har et foreningskontor på Chateau Neuf der man kan bruke utstyret vårt og henge med andre medlemmer! Vi har blant annet alle de nyeste konsollene, 6 gaming-PCer samt et bredt utvalg av brettspill.", + "tip": "Tips: Du kan kikke rundt i kontoret ved å klikke og dra på bildet nedenfor 👀" + }, + "boardMembers": { + "title": "Styret", + "imageAlt": "Bilde av styremedlem", + "noContact": "Ingen kontaktinfo" + }, + "contact": { + "title": "Finn oss", + "email": "E-post" + }, + "footer": { + "sourceCode": "Kildekode", + "admin": "Adminområdet", + "orgNum": "Org.Nr." + } +} diff --git a/next.config.ts b/next.config.ts index e993b97..66e7bc6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,4 +1,7 @@ import type { NextConfig } from "next" +import createNextIntlPlugin from "next-intl/plugin" + +const withNextIntl = createNextIntlPlugin() const nextConfig: NextConfig = { images: { @@ -13,4 +16,4 @@ const nextConfig: NextConfig = { }, } -export default nextConfig +export default withNextIntl(nextConfig) diff --git a/package-lock.json b/package-lock.json index 1bcc742..acad805 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,10 @@ "@portabletext/react": "^3.2.1", "@sanity/client": "^7.8.1", "@sanity/image-url": "^1.1.0", + "country-flag-icons": "^1.5.19", "install": "^0.13.0", "next": "15.4.4", + "next-intl": "^4.3.4", "npm": "^11.5.2", "react": "19.1.0", "react-dom": "19.1.0", @@ -204,6 +206,66 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", + "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.1", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", + "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", + "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/icu-skeleton-parser": "1.8.14", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", + "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -990,6 +1052,12 @@ "node": ">=10.0.0" } }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2110,6 +2178,12 @@ "dev": true, "license": "MIT" }, + "node_modules/country-flag-icons": { + "version": "1.5.19", + "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.19.tgz", + "integrity": "sha512-D/ZkRyj+ywJC6b2IrAN3/tpbReMUqmuRLlcKFoY/o0+EPQN9Ev/e8tV+D3+9scvu/tarxwLErNwS73C3yzxs/g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2211,6 +2285,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/decompress-response": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", @@ -3464,6 +3544,18 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.7.16", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", + "integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.2", + "tslib": "^2.8.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4181,6 +4273,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "15.4.4", "resolved": "https://registry.npmjs.org/next/-/next-15.4.4.tgz", @@ -4233,6 +4334,33 @@ } } }, + "node_modules/next-intl": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.4.tgz", + "integrity": "sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^1.0.0", + "use-intl": "^4.3.4" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/npm": { "version": "11.5.2", "resolved": "https://registry.npmjs.org/npm/-/npm-11.5.2.tgz", @@ -7982,7 +8110,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8062,6 +8190,20 @@ "punycode": "^2.1.0" } }, + "node_modules/use-intl": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.4.tgz", + "integrity": "sha512-sHfiU0QeJ1rirNWRxvCyvlSh9+NczcOzRnPyMeo2rtHXhVnBsvMRjE+UG4eh3lRhCxrvcqei/I0lBxsc59on1w==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "@schummar/icu-type-parser": "1.21.5", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 187ff69..9454940 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,10 @@ "@portabletext/react": "^3.2.1", "@sanity/client": "^7.8.1", "@sanity/image-url": "^1.1.0", + "country-flag-icons": "^1.5.19", "install": "^0.13.0", "next": "15.4.4", + "next-intl": "^4.3.4", "npm": "^11.5.2", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index ed4253e..b20dd21 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -9,6 +9,7 @@ import { getBlogPost } from "@/lib/sanity" import { PortableText, PortableTextComponents } from "@portabletext/react" import { Metadata, Viewport } from "next" +import { getLocale } from "next-intl/server" import Image from "next/image" import * as styles from "./page.module.css" @@ -98,6 +99,7 @@ const components: PortableTextComponents = { const BlogPost = async ({ params }: BlogPageParams) => { const { slug }: { slug: string } = await params + const locale = await getLocale() const data = await getBlogPost(slug) @@ -105,7 +107,7 @@ const BlogPost = async ({ params }: BlogPageParams) => { throw new Error("Blog post not found") } - const createdAt = new Date(data._createdAt).toLocaleDateString("nb-NO", { + const createdAt = new Date(data._createdAt).toLocaleDateString(locale, { day: "numeric", month: "long", year: "numeric", diff --git a/src/app/event/[slug]/page.tsx b/src/app/event/[slug]/page.tsx index 145df44..27646af 100644 --- a/src/app/event/[slug]/page.tsx +++ b/src/app/event/[slug]/page.tsx @@ -4,6 +4,7 @@ import Back from "@/components/ui/back" import { getEvent } from "@/lib/sanity" import { Metadata, Viewport } from "next" +import { getLocale } from "next-intl/server" import { FaCalendarAlt, FaMapMarkerAlt } from "react-icons/fa" import * as styles from "./page.module.css" @@ -52,6 +53,7 @@ export function generateViewport(): Viewport { const Event = async ({ params }: EventPageParams) => { const { slug }: { slug: string } = await params + const locale = await getLocale() const data = await getEvent(slug) @@ -71,7 +73,7 @@ const Event = async ({ params }: EventPageParams) => { timeZone: "Europe/Oslo", } - return date.toLocaleString("no", options) + return date.toLocaleString(locale, options) } return ( diff --git a/src/app/global-not-found.tsx b/src/app/global-not-found.tsx index 10d861a..98cab81 100644 --- a/src/app/global-not-found.tsx +++ b/src/app/global-not-found.tsx @@ -3,6 +3,7 @@ import Layout from "@/app/layout" import { generateSeoMetadata, generateSeoViewport } from "@/components/seo" import { Metadata, Viewport } from "next" +import { useTranslations } from "next-intl" import Link from "next/link" import * as styles from "./global-not-found.module.css" @@ -19,18 +20,20 @@ export function generateViewport(): Viewport { } const NotFoundPage = () => { + const t = useTranslations("404") + return (
-

404: Siden ble ikke funnet

+

{t("title")}

- Reisen vår endte her. Vennligst ikke forlat oss + {t("text")}{" "} 👀

- Tilbake til trygg grunn + {t("back")}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 65aa98a..3852315 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,12 @@ 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", @@ -17,24 +19,29 @@ export const metadata: Metadata = { }, } -export default function RootLayout({ +export default async 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 ( - + -
{children}
- {analyticsConfig?.enabled && ( - - )} + +
{children}
+ {analyticsConfig?.enabled && ( + + )} +
) diff --git a/src/components/localeProvider.module.css b/src/components/localeProvider.module.css new file mode 100644 index 0000000..d919919 --- /dev/null +++ b/src/components/localeProvider.module.css @@ -0,0 +1,24 @@ +.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; +} diff --git a/src/components/localeProvider.tsx b/src/components/localeProvider.tsx new file mode 100644 index 0000000..4ec7441 --- /dev/null +++ b/src/components/localeProvider.tsx @@ -0,0 +1,63 @@ +"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 + children: ReactNode +} + +export function LocaleProvider({ + locale: serverLocale, + messages, + children, +}: LocaleProviderProps) { + const [locale, setLocale] = useState<"no" | "en">(serverLocale) + const [loadedMessages, setLoadedMessages] = + useState>(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 ( + + {children} + +
+ {locale === "no" ? ( + + ) : ( + + )} +
+
+ ) +} diff --git a/src/components/page-sections/about.tsx b/src/components/page-sections/about.tsx index b7b8efd..7ce5e1c 100644 --- a/src/components/page-sections/about.tsx +++ b/src/components/page-sections/about.tsx @@ -2,38 +2,35 @@ import DiscordButton from "@components/ui/discordButton" import MemberButton from "@components/ui/memberButton" import OtherButton from "@components/ui/otherButton" +import { useTranslations } from "next-intl" import Image from "next/image" import * as styles from "./about.module.css" const About = () => { + const t = useTranslations("about") + const tCommon = useTranslations("common") + return (
To medlemmer spiller sammen -

Foto: 장태민, Studentenes Fotoklubb

+

+ {tCommon("photo")}: 장태민, Studentenes Fotoklubb +

-

Hvem er vi?

+

{t("title")}

-

- UiO Gaming er en forening med hovedfokus på å danne et - inkluderende fellesskap rundt videospill og e-sport. Hos oss er - det enkelt å skaffe seg venner, ettersom alle har felles - interesser. Vi arrangerer ukentlige spillkvelder og fysiske - sosiale arrangementer, som for eksempel brettspillkvelder og - sosiale sammenkomster. Vår hovedkanal er en aktiv Discord-server - hvor man kan møte nye folk med felles interesser og game med andre - studenter. -

+

{t("text")}

-

Interessert?

+

{t("interested")}

diff --git a/src/components/page-sections/boardMembers.tsx b/src/components/page-sections/boardMembers.tsx index 2968411..685c2c4 100644 --- a/src/components/page-sections/boardMembers.tsx +++ b/src/components/page-sections/boardMembers.tsx @@ -1,6 +1,7 @@ import { urlFor } from "@/lib/imageUrlBuilder" import { sanityClient } from "@/lib/sanity" +import { getLocale, getTranslations } from "next-intl/server" import Image from "next/image" import * as styles from "./boardMembers.module.css" @@ -27,6 +28,9 @@ interface BoardMember { } const BoardMembers = async () => { + const t = await getTranslations("boardMembers") + const locale = (await getLocale()) as "no" | "en" + const query = ` *[_type == "author" && boardMember == true] | order(orderRank asc) { _id, @@ -53,7 +57,7 @@ const BoardMembers = async () => { return (
-

Styret

+

{t("title")}

{data.map((member: BoardMember) => (
@@ -73,21 +77,21 @@ const BoardMembers = async () => { width={256} height={256} className="circular" - alt={`Bilde av styremedlem, ${member.name}`} + alt={`${t("imageAlt")}, ${member.name}`} />

{member.name}

-

{member.role?.no}

+

{member.role?.[locale]}

-

{member.bio?.no}

+

{member.bio?.[locale]}

{member.contactInfo ? ( member.contactInfo ) : ( - Ingen kontaktinfo + {t("noContact")} )}

diff --git a/src/components/page-sections/contact.tsx b/src/components/page-sections/contact.tsx index fc459e7..687d69d 100644 --- a/src/components/page-sections/contact.tsx +++ b/src/components/page-sections/contact.tsx @@ -1,3 +1,5 @@ +import { useTranslations } from "next-intl" + import * as styles from "./contact.module.css" import { @@ -12,14 +14,16 @@ import { } from "react-icons/fa" const Contact = () => { + const t = useTranslations("contact") + return (
-

Finn oss

+

{t("title")}

diff --git a/src/components/page-sections/events.tsx b/src/components/page-sections/events.tsx index 59cabff..28fcded 100644 --- a/src/components/page-sections/events.tsx +++ b/src/components/page-sections/events.tsx @@ -1,5 +1,6 @@ import { getAllEvents } from "@/lib/sanity" +import { getLocale, getTranslations } from "next-intl/server" import Image from "next/image" import Link from "next/link" import { FaCalendarAlt, FaMapMarkerAlt } from "react-icons/fa" @@ -18,6 +19,10 @@ interface Event { } const Events = async () => { + const t = await getTranslations("events") + const tCommon = await getTranslations("common") + const locale = await getLocale() + const data: Event[] = await getAllEvents() const currentDate = new Date() @@ -39,7 +44,7 @@ const Events = async () => { timeZone: "Europe/Oslo", } - return date.toLocaleString("no", options) + return date.toLocaleString(locale, options) } const mapEvents = (events: Event[]) => { @@ -64,7 +69,7 @@ const Events = async () => { ) : ( <> {event.description.substring(0, 70)}...{" "} - Les mer + {t("readMore")} )}

@@ -72,14 +77,21 @@ const Events = async () => { )) } else { - return

Ingen kommende arrangementer

+ return ( +

+ {t("noEvents")} +
+
+ (っ˘̩╭╮˘̩)っ +

+ ) } } return (
-

Hva skjer?

+

{t("title")}

{mapEvents(filteredEvents)}
{ width={850} height={480} style={{ borderRadius: "1rem", objectFit: "cover", height: "100%" }} - alt="Medlemmer spiller brettspill" + alt={t("imageAlt")} /> -

Foto: 장태민, Studentenes Fotoklubb

+

+ {tCommon("photo")}: 장태민, Studentenes Fotoklubb +

diff --git a/src/components/page-sections/footer.tsx b/src/components/page-sections/footer.tsx index d8cb326..802d587 100644 --- a/src/components/page-sections/footer.tsx +++ b/src/components/page-sections/footer.tsx @@ -1,8 +1,18 @@ +import { useLocale, useTranslations } from "next-intl" import Image from "next/image" import * as styles from "./footer.module.css" const Footer = () => { + const t = useTranslations("footer") + const tCommon = useTranslations("common") + const locale = useLocale() + + const statutesUrl = + locale === "en" + ? "https://statutes.uiogaming.no" + : "https://vedtekter.uiogaming.no" + return (