From 64909965e1fdd95b7e57a0f97505cb5168929732 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 18 Aug 2023 06:31:41 +0800 Subject: [PATCH] feat: language switch (#903) --- src/app/(home)/layout.tsx | 2 ++ src/components/common/LanguageSwitch.tsx | 37 +++++++++++++++++++ src/components/site/SiteFooter.tsx | 6 +++- src/components/ui/Menu.tsx | 4 +-- src/lib/accept-lang.ts | 8 ++++- src/lib/i18n/client.ts | 46 +++++++++++++++++++++++- src/lib/i18n/settings.ts | 7 ++++ 7 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 src/components/common/LanguageSwitch.tsx diff --git a/src/app/(home)/layout.tsx b/src/app/(home)/layout.tsx index 02dc6bfe..3f39b422 100644 --- a/src/app/(home)/layout.tsx +++ b/src/app/(home)/layout.tsx @@ -1,5 +1,6 @@ import { ConnectButton } from "~/components/common/ConnectButton" import { DarkModeSwitch } from "~/components/common/DarkModeSwitch" +import { LanguageSwitch } from "~/components/common/LanguageSwitch" import { Logo } from "~/components/common/Logo" import HomeTabs from "~/components/home/HomeTabs" import PromotionLinks from "~/components/home/PromotionLinks" @@ -45,6 +46,7 @@ export default async function HomeLayout({ + ©{" "} + } + dropdown={ + <> + {languages.map((language, i) => ( + { + changeLanguage(language.code) + }} + className="mx-auto" + > + {language.name} + {language.active && ( + + )} + + ))} + + } + /> + ) +} diff --git a/src/components/site/SiteFooter.tsx b/src/components/site/SiteFooter.tsx index 1f863869..97ed50c0 100644 --- a/src/components/site/SiteFooter.tsx +++ b/src/components/site/SiteFooter.tsx @@ -6,6 +6,7 @@ import { Trans, getTranslation } from "~/lib/i18n" import { ExpandedCharacter } from "~/lib/types" import { DarkModeSwitch } from "../common/DarkModeSwitch" +import { LanguageSwitch } from "../common/LanguageSwitch" import { UniLink } from "../ui/UniLink" import ConnectedAccounts from "./ConnectedAccounts" @@ -52,7 +53,10 @@ export default async function SiteFooter({ - +
+ + +
{site?.metadata?.content?.ga && ( diff --git a/src/components/ui/Menu.tsx b/src/components/ui/Menu.tsx index 27ae2a98..caf7dd2c 100644 --- a/src/components/ui/Menu.tsx +++ b/src/components/ui/Menu.tsx @@ -1,7 +1,7 @@ import Link from "next/link" import React, { Fragment } from "react" -import { autoUpdate, shift, useFloating } from "@floating-ui/react" +import { Placement, autoUpdate, shift, useFloating } from "@floating-ui/react" import { Menu as HeadlessUiMenu } from "@headlessui/react" import { cn } from "~/lib/utils" @@ -19,7 +19,7 @@ export function Menu({ }: React.PropsWithChildren<{ target: JSX.Element dropdown: React.ReactNode - placement?: "bottom-start" | "bottom-end" + placement?: Placement }>) { const { refs, floatingStyles } = useFloating({ placement, diff --git a/src/lib/accept-lang.ts b/src/lib/accept-lang.ts index 5fc14a07..a12b884d 100644 --- a/src/lib/accept-lang.ts +++ b/src/lib/accept-lang.ts @@ -1,19 +1,25 @@ -import { headers } from "next/headers" +import { cookies, headers } from "next/headers" import { fallbackLng } from "~/lib/i18n/settings" export function getAcceptLang() { let lng = fallbackLng + const preferLang = cookies().get("preferred_language")?.value + let acceptLang = headers().get("accept-language")?.split(",")[0] if (acceptLang === "zh-HK" || acceptLang === "zh-TW") { acceptLang = "zh-TW" } else { acceptLang = acceptLang?.split("-")[0] } + if (acceptLang) { lng = acceptLang } + if (preferLang) { + lng = preferLang + } return lng } diff --git a/src/lib/i18n/client.ts b/src/lib/i18n/client.ts index 70cbba83..3b644720 100644 --- a/src/lib/i18n/client.ts +++ b/src/lib/i18n/client.ts @@ -2,6 +2,7 @@ import i18next from "i18next" import resourcesToBackend from "i18next-resources-to-backend" +import { useEffect, useState } from "react" import { initReactI18next, useTranslation as useTranslationOrg, @@ -10,7 +11,7 @@ import { Trans as TransW } from "react-i18next/TransWithoutContext" import { useLang } from "~/hooks/useLang" -import { defaultNS, getOptions, languages } from "./settings" +import { defaultNS, getOptions, languageNames, languages } from "./settings" i18next .use(initReactI18next) @@ -33,3 +34,46 @@ export function useTranslation(ns: string = defaultNS) { } export const Trans = TransW + +export function isLanguageAuto() { + return !window.document.cookie.includes("preferred_language") +} + +export function changeLanguage(language: string) { + if (language === "auto") { + document.cookie = + "preferred_language=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" + } else { + document.cookie = `preferred_language=${language};` + } + window.location.reload() +} + +export function useAvailableLanguages() { + const supportedLngs = i18next.options.supportedLngs + const languages = + typeof supportedLngs === "object" + ? supportedLngs.filter((i) => i !== "cimode") + : [] + const [isMounted, setIsMounted] = useState(false) + + const [currentLng, setCurrentLng] = useState(i18next.language) + + useEffect(() => { + if (!isMounted) { + setIsMounted(true) + return + } + if (isMounted && isLanguageAuto()) { + setCurrentLng("auto") + } + }, [isMounted]) + + return languages.concat("auto").map((lang) => { + return { + code: lang, + name: languageNames[lang as keyof typeof languageNames], + active: lang === currentLng, + } + }) +} diff --git a/src/lib/i18n/settings.ts b/src/lib/i18n/settings.ts index 4da7f705..7b70c27a 100644 --- a/src/lib/i18n/settings.ts +++ b/src/lib/i18n/settings.ts @@ -1,5 +1,12 @@ export const fallbackLng = "en" export const languages = ["en", "zh", "zh-TW", "ja"] +export const languageNames = { + en: "English", + zh: "中文", + "zh-TW": "中文(繁體)", + ja: "日本語", + auto: "Auto", +} export const defaultNS = "common" export function getOptions(lng = fallbackLng, ns = defaultNS) {