feat: language switch (#903)

This commit is contained in:
Stephen Zhou 2023-08-18 06:31:41 +08:00 committed by GitHub
parent f822b29915
commit 64909965e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 5 deletions

View File

@ -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({
<PromotionLinks className="w-full sm:w-72" />
<span className="inline-flex items-center space-y-4 sm:space-y-0 sm:space-x-4 mx-auto sm:mx-0 mt-10 sm:mt-0 flex-col sm:flex-row">
<DarkModeSwitch />
<LanguageSwitch />
<span>
&copy;{" "}
<UniLink

View File

@ -0,0 +1,37 @@
"use client"
import { changeLanguage, useAvailableLanguages } from "~/lib/i18n/client"
import { Menu } from "../ui/Menu"
export function LanguageSwitch() {
const languages = useAvailableLanguages()
return (
<Menu
placement="top"
target={
<button className="inline-block icon-[mingcute--translate-2-line] text-2xl"></button>
}
dropdown={
<>
{languages.map((language, i) => (
<Menu.Item
key={i}
type="button"
onClick={() => {
changeLanguage(language.code)
}}
className="mx-auto"
>
<span>{language.name}</span>
{language.active && (
<span className="ml-2 icon-[mingcute--check-line]"></span>
)}
</Menu.Item>
))}
</>
}
/>
)
}

View File

@ -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({
<ConnectedAccounts
connectedAccounts={site?.metadata?.content?.connected_accounts}
/>
<DarkModeSwitch />
<div className="flex gap-x-2 items-center">
<LanguageSwitch />
<DarkModeSwitch />
</div>
</div>
</footer>
{site?.metadata?.content?.ga && (

View File

@ -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,

View File

@ -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
}

View File

@ -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,
}
})
}

View File

@ -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) {