From fae86142e7a4ea65b97c72261f53ffb26ca94471 Mon Sep 17 00:00:00 2001 From: 6drf21e Date: Wed, 3 Apr 2024 09:51:35 +0800 Subject: [PATCH] Add language support and localization files --- web/src/app/components/answer.tsx | 3 +- web/src/app/components/footer.tsx | 7 +-- web/src/app/components/relates.tsx | 7 ++- web/src/app/components/result.tsx | 6 +-- web/src/app/components/search.tsx | 3 +- web/src/app/components/sources.tsx | 3 +- web/src/app/components/title.tsx | 4 +- web/src/app/locales/cn.ts | 36 +++++++++++++ web/src/app/locales/en.ts | 30 +++++++++++ web/src/app/locales/index.ts | 75 ++++++++++++++++++++++++++++ web/src/app/locales/jp.ts | 30 +++++++++++ web/src/app/page.tsx | 18 +++++++ web/src/app/utils/parse-streaming.ts | 2 + 13 files changed, 210 insertions(+), 14 deletions(-) create mode 100644 web/src/app/locales/cn.ts create mode 100644 web/src/app/locales/en.ts create mode 100644 web/src/app/locales/index.ts create mode 100644 web/src/app/locales/jp.ts diff --git a/web/src/app/components/answer.tsx b/web/src/app/components/answer.tsx index 234215d..f398075 100644 --- a/web/src/app/components/answer.tsx +++ b/web/src/app/components/answer.tsx @@ -10,6 +10,7 @@ import { BookOpenText } from "lucide-react"; import { FC } from "react"; import Markdown from "react-markdown"; import Image from "next/image"; +import Locale from "../locales"; export const Answer: FC<{ markdown: string; sources: Source[] }> = ({ markdown, @@ -19,7 +20,7 @@ export const Answer: FC<{ markdown: string; sources: Source[] }> = ({ - Answer + {Locale.Answer.answer} } content={ diff --git a/web/src/app/components/footer.tsx b/web/src/app/components/footer.tsx index cea3f4d..d296f7f 100644 --- a/web/src/app/components/footer.tsx +++ b/web/src/app/components/footer.tsx @@ -1,13 +1,10 @@ import { Mails } from "lucide-react"; import { FC } from "react"; - +import Locale from "../locales"; export const Footer: FC = () => { return (
-
- Answer generated by large language models, plz double check for - correctness. -
+
{Locale.Footer.statement}
= ({ relates }) => { return ( - Related + {Locale.Relates.related} } content={ @@ -21,7 +22,9 @@ export const Relates: FC<{ relates: Relate[] | null }> = ({ relates }) => { )) ) : ( -
No related questions.
+
+ {Locale.Relates.no_related_questions} +
) ) : ( <> diff --git a/web/src/app/components/result.tsx b/web/src/app/components/result.tsx index f146f95..d3ddeb5 100644 --- a/web/src/app/components/result.tsx +++ b/web/src/app/components/result.tsx @@ -7,6 +7,7 @@ import { Source } from "@/app/interfaces/source"; import { parseStreaming } from "@/app/utils/parse-streaming"; import { Annoyed } from "lucide-react"; import { FC, useEffect, useState } from "react"; +import Locale, { getLang } from "../locales"; export const Result: FC<{ query: string; rid: string }> = ({ query, rid }) => { const [sources, setSources] = useState([]); @@ -19,6 +20,7 @@ export const Result: FC<{ query: string; rid: string }> = ({ query, rid }) => { controller, query, rid, + getLang(), setSources, setMarkdown, setRelates, @@ -37,9 +39,7 @@ export const Result: FC<{ query: string; rid: string }> = ({ query, rid }) => {
- {error === 429 - ? "Sorry, you have made too many requests recently, try again later." - : "Sorry, we might be overloaded, try again later."} + {error === 429 ? Locale.Err[429] : Locale.Err[500]}
)} diff --git a/web/src/app/components/search.tsx b/web/src/app/components/search.tsx index d95bbef..e4ab394 100644 --- a/web/src/app/components/search.tsx +++ b/web/src/app/components/search.tsx @@ -5,6 +5,7 @@ import { nanoid } from "nanoid"; import { useRouter } from "next/navigation"; import React, { FC, useState } from "react"; import { useSearchParams } from "next/navigation"; +import Locale from "../locales"; interface SearchProps { useContinueButton?: boolean; // true: 使用“继续对话”按钮; false: 使用“新的搜索”按钮 @@ -41,7 +42,7 @@ export const Search: FC = ({ useContinueButton = false }) => { value={value} onChange={(e) => setValue(e.target.value)} autoFocus - placeholder="Ask me anything ..." + placeholder={Locale.Search.placeholder} className="px-2 pr-6 w-full rounded-md flex-1 outline-none bg-white" />
diff --git a/web/src/app/locales/cn.ts b/web/src/app/locales/cn.ts new file mode 100644 index 0000000..313e298 --- /dev/null +++ b/web/src/app/locales/cn.ts @@ -0,0 +1,36 @@ +const cn = { + Err: { + 429: "请求过于频繁,请稍后再试", + 500: "抱歉,我们可能负载过重,请稍后再试。", + }, + Title: { + rewrite: "重写", + }, + Search: { + placeholder: "有问必答...", + }, + Footer: { + statement: "这是由大语言模型提供的答案, 请务必核实正确性。", + }, + Relates: { + no_related_questions: "没有相关问题。", + related: "相关", + }, + Answer: { + answer: "答案", + }, + Sources: { + sources: "来源", + }, +}; + +type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + +export type LocaleType = typeof cn; +export type PartialLocaleType = DeepPartial; + +export default cn; diff --git a/web/src/app/locales/en.ts b/web/src/app/locales/en.ts new file mode 100644 index 0000000..bf42d40 --- /dev/null +++ b/web/src/app/locales/en.ts @@ -0,0 +1,30 @@ +import { LocaleType } from "./index"; + +const en: LocaleType = { + Err: { + 429: "Sorry, you have made too many requests recently, try again later.", + 500: "Sorry, we might be overloaded, try again later.", + }, + Title: { + rewrite: "Rewrite", + }, + Search: { + placeholder: "Ask me anything ...", + }, + Footer: { + statement: + "Answer generated by large language models, plz double check for correctness.", + }, + Relates: { + no_related_questions: "No related questions.", + related: "Related", + }, + Answer: { + answer: "Answer", + }, + Sources: { + sources: "Sources", + }, +}; + +export default en; diff --git a/web/src/app/locales/index.ts b/web/src/app/locales/index.ts new file mode 100644 index 0000000..c24d4a1 --- /dev/null +++ b/web/src/app/locales/index.ts @@ -0,0 +1,75 @@ +// reference source: https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/blob/main/app/locales/index.ts +import cn from "./cn"; +import en from "./en"; +import jp from "./jp"; + +import type { LocaleType } from "./cn"; +export type { LocaleType, PartialLocaleType } from "./cn"; + +const ALL_LANGS = { + en, + cn, + jp, +}; + +export type Lang = keyof typeof ALL_LANGS; + +export const AllLangs = Object.keys(ALL_LANGS) as Lang[]; + +export const ALL_LANG_OPTIONS: Record = { + en: "English", + cn: "简体中文", + jp: "日本語", +}; + +const LANG_KEY = "lang"; +const DEFAULT_LANG = "en"; + +const targetLang = ALL_LANGS[getLang()] as LocaleType; + +export default targetLang as LocaleType; + +function getItem(key: string) { + try { + return localStorage.getItem(key); + } catch { + return null; + } +} + +function setItem(key: string, value: string) { + try { + localStorage.setItem(key, value); + } catch {} +} + +function getLanguage() { + try { + return navigator.language.toLowerCase(); + } catch { + return DEFAULT_LANG; + } +} + +export function getLang(): Lang { + const savedLang = getItem(LANG_KEY); + + if (AllLangs.includes((savedLang ?? "") as Lang)) { + return savedLang as Lang; + } + + const lang = getLanguage(); + + for (const option of AllLangs) { + if (lang.includes(option)) { + return option; + } + } + + return DEFAULT_LANG; +} + +export function changeLang(lang: Lang) { + setItem(LANG_KEY, lang); + location.reload(); +} diff --git a/web/src/app/locales/jp.ts b/web/src/app/locales/jp.ts new file mode 100644 index 0000000..c53a235 --- /dev/null +++ b/web/src/app/locales/jp.ts @@ -0,0 +1,30 @@ +import { LocaleType } from "./index"; + +const jp: LocaleType = { + Err: { + 429: "申し訳ありませんが、最近のリクエストが多すぎます。後ほど再試行してください。", + 500: "申し訳ありませんが、サーバーが過負荷の可能性があります。後ほど再試行してください。", + }, + Title: { + rewrite: "書き直し", + }, + Search: { + placeholder: "何でも聞いてください...", + }, + Footer: { + statement: + "回答は大規模な言語モデルによって生成されます。正確性を再確認してください。", + }, + Relates: { + no_related_questions: "関連する質問はありません。", + related: "関連性", + }, + Answer: { + answer: "回答", + }, + Sources: { + sources: "情報源", + }, +}; + +export default jp; diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index a70e0bc..86bde44 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -4,10 +4,28 @@ import { Logo } from "@/app/components/logo"; import { PresetQuery } from "@/app/components/preset-query"; import { Search } from "@/app/components/search"; import React from "react"; +import { AllLangs, ALL_LANG_OPTIONS, changeLang, getLang } from "./locales"; export default function Home() { return (
+ {/* 语言下拉菜单 */} +
+ +
diff --git a/web/src/app/utils/parse-streaming.ts b/web/src/app/utils/parse-streaming.ts index 0667514..a2f1475 100644 --- a/web/src/app/utils/parse-streaming.ts +++ b/web/src/app/utils/parse-streaming.ts @@ -9,6 +9,7 @@ export const parseStreaming = async ( controller: AbortController, query: string, search_uuid: string, + lang: string, onSources: (value: Source[]) => void, onMarkdown: (value: string) => void, onRelates: (value: Relate[]) => void, @@ -28,6 +29,7 @@ export const parseStreaming = async ( body: JSON.stringify({ query, search_uuid, + lang, }), }); if (response.status !== 200) {