Add language dropdown localization
This commit is contained in:
@@ -2,7 +2,7 @@ import type {Metadata} from "next";
|
||||
import {NextIntlClientProvider, hasLocale} from "next-intl";
|
||||
import {notFound} from "next/navigation";
|
||||
import "../globals.css";
|
||||
import {routing, type Locale} from "@/i18n/routing";
|
||||
import {localeDirections, routing, type Locale} from "@/i18n/routing";
|
||||
|
||||
type LocaleLayoutProps = Readonly<{
|
||||
children: React.ReactNode;
|
||||
@@ -70,11 +70,10 @@ export default async function LocaleLayout({
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang={locale.split("-")[0]}>
|
||||
<html lang={locale} dir={localeDirections[locale]}>
|
||||
<body>
|
||||
<NextIntlClientProvider>{children}</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import {useEffect, useRef} from "react";
|
||||
import {animate, createTimeline, stagger} from "animejs";
|
||||
import {useLocale, useTranslations} from "next-intl";
|
||||
import {Link} from "@/i18n/navigation";
|
||||
import {routing, type Locale} from "@/i18n/routing";
|
||||
import {Link, useRouter} from "@/i18n/navigation";
|
||||
import {localeNames, routing, type Locale} from "@/i18n/routing";
|
||||
|
||||
type Service = {
|
||||
number: string;
|
||||
@@ -23,6 +23,7 @@ type ProcessStep = {
|
||||
export default function Home() {
|
||||
const rootRef = useRef<HTMLElement>(null);
|
||||
const locale = useLocale() as Locale;
|
||||
const router = useRouter();
|
||||
const t = useTranslations("Home");
|
||||
|
||||
const heroWords = t.raw("hero.words") as string[];
|
||||
@@ -235,25 +236,25 @@ export default function Home() {
|
||||
{t("nav.notes")}
|
||||
</a>
|
||||
</div>
|
||||
<div className="nav-item hidden items-center gap-2 lg:flex">
|
||||
<span className="sr-only">{t("localeSwitcher.label")}</span>
|
||||
{routing.locales.map((option) => (
|
||||
<Link
|
||||
key={option}
|
||||
href="/"
|
||||
locale={option}
|
||||
aria-current={option === locale ? "page" : undefined}
|
||||
style={option === locale ? {color: "var(--paper)"} : undefined}
|
||||
className={`border px-3 py-2 transition ${
|
||||
option === locale
|
||||
? "border-[var(--navy)] bg-[var(--navy)] text-[var(--paper)]"
|
||||
: "border-[var(--line)] hover:border-[var(--navy)]"
|
||||
}`}
|
||||
>
|
||||
{option.split("-")[1].toUpperCase()}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<label className="nav-item hidden items-center gap-3 lg:flex">
|
||||
<span className="text-[var(--ink-muted)]">
|
||||
{t("localeSwitcher.label")}
|
||||
</span>
|
||||
<select
|
||||
value={locale}
|
||||
aria-label={t("localeSwitcher.label")}
|
||||
onChange={(event) => {
|
||||
router.replace("/", {locale: event.target.value as Locale});
|
||||
}}
|
||||
className="min-w-40 border border-[var(--line)] bg-transparent px-3 py-2 text-[var(--navy)] outline-none transition hover:border-[var(--navy)] focus:border-[var(--navy)]"
|
||||
>
|
||||
{routing.locales.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{localeNames[option]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<a
|
||||
href="mailto:hello@kairas.io"
|
||||
className="nav-item border border-[var(--navy)] px-4 py-3 transition hover:bg-[var(--navy)] hover:text-[var(--paper)]"
|
||||
@@ -261,27 +262,25 @@ export default function Home() {
|
||||
{t("nav.contact")}
|
||||
</a>
|
||||
</nav>
|
||||
<div className="flex items-center justify-center gap-2 border-t border-[var(--line)] px-5 py-2 text-[11px] uppercase leading-none tracking-[0.12em] lg:hidden">
|
||||
<label className="flex items-center justify-center gap-3 border-t border-[var(--line)] px-5 py-2 text-[11px] uppercase leading-none tracking-[0.12em] lg:hidden">
|
||||
<span className="text-[var(--ink-muted)]">
|
||||
{t("localeSwitcher.label")}
|
||||
</span>
|
||||
{routing.locales.map((option) => (
|
||||
<Link
|
||||
key={option}
|
||||
href="/"
|
||||
locale={option}
|
||||
aria-current={option === locale ? "page" : undefined}
|
||||
style={option === locale ? {color: "var(--paper)"} : undefined}
|
||||
className={`border px-3 py-2 transition ${
|
||||
option === locale
|
||||
? "border-[var(--navy)] bg-[var(--navy)] text-[var(--paper)]"
|
||||
: "border-[var(--line)] hover:border-[var(--navy)]"
|
||||
}`}
|
||||
>
|
||||
{option.split("-")[1].toUpperCase()}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<select
|
||||
value={locale}
|
||||
aria-label={t("localeSwitcher.label")}
|
||||
onChange={(event) => {
|
||||
router.replace("/", {locale: event.target.value as Locale});
|
||||
}}
|
||||
className="min-w-36 border border-[var(--line)] bg-transparent px-3 py-2 text-[var(--navy)] outline-none"
|
||||
>
|
||||
{routing.locales.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{localeNames[option]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</header>
|
||||
|
||||
<section className="relative z-10 mx-auto grid min-h-screen max-w-[1500px] content-end gap-12 px-5 pb-12 pt-40 sm:px-8 md:pt-36 lg:grid-cols-[1.05fr_0.95fr] lg:px-12">
|
||||
|
||||
@@ -2,15 +2,43 @@ import {getRequestConfig} from "next-intl/server";
|
||||
import {hasLocale} from "next-intl";
|
||||
import {routing} from "./routing";
|
||||
|
||||
type Messages = Record<string, unknown>;
|
||||
|
||||
function mergeMessages(base: Messages, override: Messages): Messages {
|
||||
const merged: Messages = {...base};
|
||||
|
||||
for (const [key, value] of Object.entries(override)) {
|
||||
const baseValue = merged[key];
|
||||
if (
|
||||
value &&
|
||||
baseValue &&
|
||||
typeof value === "object" &&
|
||||
typeof baseValue === "object" &&
|
||||
!Array.isArray(value) &&
|
||||
!Array.isArray(baseValue)
|
||||
) {
|
||||
merged[key] = mergeMessages(baseValue as Messages, value as Messages);
|
||||
} else {
|
||||
merged[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
export default getRequestConfig(async ({requestLocale}) => {
|
||||
const requested = await requestLocale;
|
||||
const locale = hasLocale(routing.locales, requested)
|
||||
? requested
|
||||
: routing.defaultLocale;
|
||||
const defaultMessages = (await import("../../messages/en.json")).default;
|
||||
const localeMessages =
|
||||
locale === routing.defaultLocale
|
||||
? defaultMessages
|
||||
: (await import(`../../messages/${locale}.json`)).default;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../../messages/${locale}.json`)).default,
|
||||
messages: mergeMessages(defaultMessages, localeMessages),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
import {defineRouting} from "next-intl/routing";
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales: ["en-ie", "en-gb", "en-us"],
|
||||
defaultLocale: "en-ie",
|
||||
locales: ["en", "zh", "hi", "es", "fr", "ar", "bn", "pt", "ru", "ur"],
|
||||
defaultLocale: "en",
|
||||
localePrefix: "always",
|
||||
localeDetection: true,
|
||||
});
|
||||
|
||||
export type Locale = (typeof routing.locales)[number];
|
||||
|
||||
export const localeNames: Record<Locale, string> = {
|
||||
en: "English",
|
||||
zh: "中文",
|
||||
hi: "हिन्दी",
|
||||
es: "Español",
|
||||
fr: "Français",
|
||||
ar: "العربية",
|
||||
bn: "বাংলা",
|
||||
pt: "Português",
|
||||
ru: "Русский",
|
||||
ur: "اردو",
|
||||
};
|
||||
|
||||
export const localeDirections: Record<Locale, "ltr" | "rtl"> = {
|
||||
en: "ltr",
|
||||
zh: "ltr",
|
||||
hi: "ltr",
|
||||
es: "ltr",
|
||||
fr: "ltr",
|
||||
ar: "rtl",
|
||||
bn: "ltr",
|
||||
pt: "ltr",
|
||||
ru: "ltr",
|
||||
ur: "rtl",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user