feat: i18n中文英文
This commit is contained in:
parent
38ede8e285
commit
d64295e27a
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
|
@ -10,9 +10,14 @@ import QuillWrapper from "@/components/QuillWrapper";
|
|||
// import SEditor from "../components/SlateEditor";
|
||||
import SettingsLink from "@/components/SettingsLink";
|
||||
import PaperManagementWrapper from "@/components/PaperManagementWrapper";
|
||||
//i18n
|
||||
import { useTranslation } from "@/app/i18n";
|
||||
import { IndexProps } from "@/utils/global";
|
||||
|
||||
// import Error from "@/app/global-error";
|
||||
export default function Index() {
|
||||
export default async function Index({ params: { lng } }: IndexProps) {
|
||||
const { t } = await useTranslation(lng);
|
||||
|
||||
const cookieStore = cookies();
|
||||
|
||||
const canInitSupabaseClient = () => {
|
||||
|
@ -39,8 +44,8 @@ export default function Index() {
|
|||
<SettingsLink />
|
||||
</div>
|
||||
</nav>
|
||||
<PaperManagementWrapper />
|
||||
<QuillWrapper />
|
||||
<PaperManagementWrapper lng={lng} />
|
||||
<QuillWrapper lng={lng} />
|
||||
<footer className="w-full border-t border-t-foreground/10 p-8 flex justify-center text-center text-xs">
|
||||
<p>
|
||||
<a
|
||||
|
@ -49,7 +54,7 @@ export default function Index() {
|
|||
className="font-bold hover:underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
give me a star in GitHub
|
||||
{t("give me a star in GitHub")}
|
||||
</a>
|
||||
</p>
|
||||
</footer>
|
12
app/[lng]/settings/page.tsx
Normal file
12
app/[lng]/settings/page.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
//这里是settings页面
|
||||
import SettingsWrapper from "@/components/SettingsWrapper";
|
||||
//i18n
|
||||
import { IndexProps } from "@/utils/global";
|
||||
|
||||
export default function settings({ params: { lng } }: IndexProps) {
|
||||
return (
|
||||
<div className="h-screen w-full ">
|
||||
<SettingsWrapper lng={lng} />
|
||||
</div>
|
||||
);
|
||||
}
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
60
app/i18n/client.js
Normal file
60
app/i18n/client.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import i18next from "i18next";
|
||||
import {
|
||||
initReactI18next,
|
||||
useTranslation as useTranslationOrg,
|
||||
} from "react-i18next";
|
||||
import { useCookies } from "react-cookie";
|
||||
import resourcesToBackend from "i18next-resources-to-backend";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { getOptions, languages, cookieName } from "./settings";
|
||||
|
||||
const runsOnServerSide = typeof window === "undefined";
|
||||
|
||||
//
|
||||
i18next
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.use(
|
||||
resourcesToBackend((language, namespace) =>
|
||||
import(`./locales/${language}/${namespace}.json`)
|
||||
)
|
||||
)
|
||||
.init({
|
||||
...getOptions(),
|
||||
lng: undefined, // let detect the language on client side
|
||||
detection: {
|
||||
order: ["path", "htmlTag", "cookie", "navigator"],
|
||||
},
|
||||
preload: runsOnServerSide ? languages : [],
|
||||
});
|
||||
|
||||
export function useTranslation(lng, ns, options) {
|
||||
const [cookies, setCookie] = useCookies([cookieName]);
|
||||
const ret = useTranslationOrg(ns, options);
|
||||
const { i18n } = ret;
|
||||
if (runsOnServerSide && lng && i18n.resolvedLanguage !== lng) {
|
||||
i18n.changeLanguage(lng);
|
||||
} else {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [activeLng, setActiveLng] = useState(i18n.resolvedLanguage);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
if (activeLng === i18n.resolvedLanguage) return;
|
||||
setActiveLng(i18n.resolvedLanguage);
|
||||
}, [activeLng, i18n.resolvedLanguage]);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
if (!lng || i18n.resolvedLanguage === lng) return;
|
||||
i18n.changeLanguage(lng);
|
||||
}, [lng, i18n]);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
if (cookies.i18next === lng) return;
|
||||
setCookie(cookieName, lng, { path: "/" });
|
||||
}, [lng, cookies.i18next]);
|
||||
}
|
||||
return ret;
|
||||
}
|
29
app/i18n/index.js
Normal file
29
app/i18n/index.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { createInstance } from "i18next";
|
||||
import resourcesToBackend from "i18next-resources-to-backend";
|
||||
import { initReactI18next } from "react-i18next/initReactI18next";
|
||||
import { getOptions } from "./settings";
|
||||
|
||||
const initI18next = async (lng, ns) => {
|
||||
const i18nInstance = createInstance();
|
||||
await i18nInstance
|
||||
.use(initReactI18next)
|
||||
.use(
|
||||
resourcesToBackend((language, namespace) =>
|
||||
import(`./locales/${language}/${namespace}.json`)
|
||||
)
|
||||
)
|
||||
.init(getOptions(lng, ns));
|
||||
return i18nInstance;
|
||||
};
|
||||
|
||||
export async function useTranslation(lng, ns, options = {}) {
|
||||
const i18nextInstance = await initI18next(lng, ns);
|
||||
return {
|
||||
t: i18nextInstance.getFixedT(
|
||||
lng,
|
||||
Array.isArray(ns) ? ns[0] : ns,
|
||||
options.keyPrefix
|
||||
),
|
||||
i18n: i18nextInstance,
|
||||
};
|
||||
}
|
29
app/i18n/locales/en/translation.json
Normal file
29
app/i18n/locales/en/translation.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"give me a star in GitHub": " give me a star in GitHub",
|
||||
"更新索引": "update paper reference index",
|
||||
"AI写作": "AI writing",
|
||||
"Paper2AI": "Paper2AI",
|
||||
"点击AI写作就是正常的对话交流,点击寻找文献会根据输入的主题词去寻找对应论文": "Click AI Write for normal conversation, click Paper2AI to find corresponding papers based on the input topic",
|
||||
"+ Add Paper": "+ Add Paper",
|
||||
"Buy VIP TO UNLOCK Cloud Sync and Edit Mutiple Papers Simultaneously": "Buy VIP TO UNLOCK Cloud Sync and Edit Mutiple Papers Simultaneously",
|
||||
"Paper Management": "Paper Management",
|
||||
"Your Cloud Papers": "Your Cloud Papers",
|
||||
"复制": "Copy",
|
||||
"添加自定义引用": "Add Custom Reference",
|
||||
"复制所有引用": "Copy All References",
|
||||
"删除所有引用": "Delete All References",
|
||||
"Title": "Title",
|
||||
"Author": "Author",
|
||||
"Year": "Year",
|
||||
"Publisher": "Publisher",
|
||||
"Url": "Url",
|
||||
"配置选择器": "Configure Selector",
|
||||
"Upstream URL:": "Upstream URL:",
|
||||
"System Prompt(Paper2AI):": "System Prompt(Paper2AI):",
|
||||
"configurations": {
|
||||
"cocopilot-gpt4": "cocopilot-gpt4 (apiKey prefix with ghu, as GitHub does not allow uploading complete keys)",
|
||||
"deepseek-chat": "deepseek-chat (Model needs to be manually changed to this one)",
|
||||
"caifree": "caifree (Recommended)",
|
||||
"custom": "Custom"
|
||||
}
|
||||
}
|
30
app/i18n/locales/zh-CN/translation.json
Normal file
30
app/i18n/locales/zh-CN/translation.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"give me a star in GitHub": "在GitHub上给我一颗star",
|
||||
"更新索引": "更新索引",
|
||||
"AI写作": "AI写作",
|
||||
"Paper2AI": "寻找文献",
|
||||
"点击AI写作就是正常的对话交流,点击寻找文献会根据输入的主题词去寻找对应论文": "点击AI写作就是正常的对话交流,点击寻找文献会根据输入的主题词去寻找对应论文",
|
||||
"+ Add Paper": "+ 添加文献",
|
||||
"Buy VIP TO UNLOCK Cloud Sync and Edit Mutiple Papers Simultaneously": "购买VIP解锁云同步和同时编辑多篇论文",
|
||||
"Paper Management": "文献管理",
|
||||
"Your Cloud Papers": "您的云端论文",
|
||||
"复制": "复制",
|
||||
"添加自定义引用": "添加自定义引用",
|
||||
"复制所有引用": "复制所有引用",
|
||||
"删除所有引用": "删除所有引用",
|
||||
"Title": "标题",
|
||||
"Author": "作者",
|
||||
"Year": "年份",
|
||||
"Publisher": "出版商",
|
||||
"Url": "论文网址",
|
||||
"配置选择器": "配置选择器",
|
||||
"Upstream URL:": "请求模型的URL:",
|
||||
"System Prompt(Paper2AI):": "系统提示(Paper2AI):",
|
||||
|
||||
"configurations": {
|
||||
"cocopilot-gpt4": "cocopilot-gpt4(apiKey前面手动加上ghu,因为GitHub不允许上传完整的密钥)",
|
||||
"deepseek-chat": "deepseek-chat(需要手动修改模型为这个)",
|
||||
"caifree": "caifree(推荐)",
|
||||
"custom": "自定义"
|
||||
}
|
||||
}
|
16
app/i18n/settings.js
Normal file
16
app/i18n/settings.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
export const fallbackLng = "en";
|
||||
export const languages = [fallbackLng, "zh-CN"];
|
||||
export const defaultNS = "translation";
|
||||
export const cookieName = "i18next";
|
||||
|
||||
export function getOptions(lng = fallbackLng, ns = defaultNS) {
|
||||
return {
|
||||
// debug: true,
|
||||
supportedLngs: languages,
|
||||
fallbackLng,
|
||||
lng,
|
||||
fallbackNS: defaultNS,
|
||||
defaultNS,
|
||||
ns,
|
||||
};
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
//这里是settings页面
|
||||
import Settings from "@/components/SettingsWrapper";
|
||||
|
||||
export default function settings() {
|
||||
return (
|
||||
<div className="h-screen w-full ">
|
||||
<Settings />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -27,6 +27,7 @@ const statePersistConfig = {
|
|||
"paperNumberRedux",
|
||||
"contentUpdatedFromNetwork",
|
||||
"isVip",
|
||||
"language",
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ export interface APIState {
|
|||
paperNumberRedux: string;
|
||||
contentUpdatedFromNetwork: boolean;
|
||||
isVip: boolean;
|
||||
language: string;
|
||||
}
|
||||
|
||||
const initialState: APIState = {
|
||||
|
@ -11,6 +12,7 @@ const initialState: APIState = {
|
|||
paperNumberRedux: "1", //默认得给个值
|
||||
contentUpdatedFromNetwork: false,
|
||||
isVip: false,
|
||||
language: "en",
|
||||
};
|
||||
|
||||
export const stateSlice = createSlice({
|
||||
|
@ -35,6 +37,9 @@ export const stateSlice = createSlice({
|
|||
setIsVip: (state, action: PayloadAction<boolean>) => {
|
||||
state.isVip = action.payload;
|
||||
},
|
||||
setLanguage: (state, action: PayloadAction<string>) => {
|
||||
state.language = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -44,6 +49,7 @@ export const {
|
|||
setPaperNumberRedux,
|
||||
setContentUpdatedFromNetwork,
|
||||
setIsVip,
|
||||
setLanguage,
|
||||
} = stateSlice.actions;
|
||||
|
||||
export const stateReducer = stateSlice.reducer;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React from "react";
|
||||
import { sendGAEvent } from "@next/third-parties/google";
|
||||
|
||||
//i18n
|
||||
import { useTranslation } from "@/app/i18n/client";
|
||||
// BuyVipButton 组件
|
||||
function BuyVipButton() {
|
||||
function BuyVipButton({ lng }: { lng: string }) {
|
||||
//i18n
|
||||
const { t } = useTranslation(lng);
|
||||
// 这是购买VIP的目标URL
|
||||
const targetUrl = "https://store.paperai.life";
|
||||
return (
|
||||
|
@ -13,7 +16,9 @@ function BuyVipButton() {
|
|||
sendGAEvent({ event: "buyVipButtonClicked", value: "buy vip" })
|
||||
}
|
||||
>
|
||||
Buy VIP TO UNLOCK Cloud Sync and Edit Mutiple Papers Simultaneously
|
||||
{t(
|
||||
"Buy VIP TO UNLOCK Cloud Sync and Edit Mutiple Papers Simultaneously"
|
||||
)}
|
||||
</button>
|
||||
</a>
|
||||
);
|
||||
|
|
|
@ -24,14 +24,18 @@ import {
|
|||
} from "@/utils/supabase/supabaseutils";
|
||||
//动画
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
import { animated, useSpring } from "@react-spring/web";
|
||||
// import { animated, useSpring } from "@react-spring/web";
|
||||
|
||||
//删除远程论文按钮
|
||||
import ParagraphDeleteButton from "@/components/ParagraphDeleteInterface";
|
||||
//vip充值按钮
|
||||
import BuyVipButton from "@/components/BuyVipButton"; // 假设这是购买VIP的按钮组件
|
||||
//i18n
|
||||
import { useTranslation } from "@/app/i18n/client";
|
||||
|
||||
const PaperManagement = () => {
|
||||
const PaperManagement = ({ lng }) => {
|
||||
//i18n
|
||||
const { t } = useTranslation(lng);
|
||||
//supabase
|
||||
const supabase = createClient();
|
||||
//redux
|
||||
|
@ -133,7 +137,10 @@ const PaperManagement = () => {
|
|||
<>
|
||||
<div className="paper-management-container flex flex-col items-center space-y-4">
|
||||
<div className="max-w-md w-full bg-blue-gray-100 rounded overflow-hidden shadow-lg mx-auto p-5">
|
||||
<h1 className="font-bold text-3xl text-center">Paper Management</h1>
|
||||
<h1 className="font-bold text-3xl text-center">
|
||||
{" "}
|
||||
{t("Paper Management")}
|
||||
</h1>
|
||||
</div>
|
||||
{isVip ? (
|
||||
<div>
|
||||
|
@ -141,10 +148,13 @@ const PaperManagement = () => {
|
|||
onClick={handleAddPaperClick}
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
+ Add Paper
|
||||
{t("+ Add Paper")}
|
||||
</button>
|
||||
<div className="flex flex-col items-center space-y-2">
|
||||
<h2 className="text-xl font-semibold">Your Papers</h2>
|
||||
<h2 className="text-xl font-semibold">
|
||||
{" "}
|
||||
{t("Your Cloud Papers")}
|
||||
</h2>
|
||||
{paperNumbers.length > 0 ? (
|
||||
<ul className="list-disc">
|
||||
{[...paperNumbers]
|
||||
|
@ -188,7 +198,7 @@ const PaperManagement = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<BuyVipButton />
|
||||
<BuyVipButton lng={lng} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import ReduxProvider from "@/app/store/ReduxProvider";
|
||||
import PaperManagement from "@/components/PaperManagement";
|
||||
|
||||
export default function PaperManagementWrapper() {
|
||||
export default function PaperManagementWrapper({ lng }) {
|
||||
return (
|
||||
<ReduxProvider>
|
||||
<PaperManagement />
|
||||
<PaperManagement lng={lng} />
|
||||
</ReduxProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ import {
|
|||
} from "@/utils/supabase/supabaseutils";
|
||||
//debounce
|
||||
import { debounce } from "lodash";
|
||||
//i18n
|
||||
import { useTranslation } from "@/app/i18n/client";
|
||||
|
||||
const toolbarOptions = [
|
||||
["bold", "italic", "underline", "strike"], // 加粗、斜体、下划线和删除线
|
||||
|
@ -60,7 +62,10 @@ const toolbarOptions = [
|
|||
["clean"], // 清除格式按钮
|
||||
];
|
||||
|
||||
const QEditor = () => {
|
||||
const QEditor = ({ lng }) => {
|
||||
//i18n
|
||||
const { t } = useTranslation(lng);
|
||||
|
||||
//读取redux中的API key
|
||||
const apiKey = useAppSelector((state: any) => state.auth.apiKey);
|
||||
const upsreamUrl = useAppSelector((state: any) => state.auth.upsreamUrl);
|
||||
|
@ -367,19 +372,21 @@ const QEditor = () => {
|
|||
value={userInput}
|
||||
onChange={handleInputChange}
|
||||
className="textarea-focus-expand flex-grow shadow appearance-none border rounded py-2 px-3 mr-2 text-grey-darker"
|
||||
placeholder="点击AI Write就是正常的对话交流,点击Paper2AI会根据输入的主题词去寻找对应论文"
|
||||
placeholder={t(
|
||||
"点击AI写作就是正常的对话交流,点击寻找文献会根据输入的主题词去寻找对应论文"
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
onClick={handleAIWrite}
|
||||
className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 mr-2 rounded"
|
||||
>
|
||||
AI Write
|
||||
{t("AI写作")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => paper2AI(userInput)}
|
||||
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 mr-2 rounded"
|
||||
>
|
||||
Paper2AI
|
||||
{t("Paper2AI")}
|
||||
</button>
|
||||
{/* 论文网站 */}
|
||||
<select
|
||||
|
@ -405,13 +412,13 @@ const QEditor = () => {
|
|||
onClick={() => formatTextInEditor(quill)} // 假设 updateIndex 是处理更新操作的函数
|
||||
className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded"
|
||||
>
|
||||
更新索引
|
||||
{t("更新索引")}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div id="editor"></div>
|
||||
<ReferenceList editor={quill} />
|
||||
<ExportDocx editor={quill} />
|
||||
<ReferenceList editor={quill} lng={lng} />
|
||||
<ExportDocx editor={quill} lng={lng} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import ReduxProvider from "@/app/store/ReduxProvider";
|
||||
import QEditor from "@/components/QuillEditor";
|
||||
|
||||
export default function QuillWrapper() {
|
||||
export default function QuillWrapper({ lng }) {
|
||||
return (
|
||||
<ReduxProvider>
|
||||
<QEditor />
|
||||
<QEditor lng={lng} />
|
||||
</ReduxProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,12 +21,16 @@ import {
|
|||
//supabase
|
||||
import { submitPaper } from "@/utils/supabase/supabaseutils";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
//i18n
|
||||
import { useTranslation } from "@/app/i18n/client";
|
||||
type ReferenceListProps = {
|
||||
editor: any;
|
||||
lng: string;
|
||||
};
|
||||
|
||||
function ReferenceList({ editor }: ReferenceListProps) {
|
||||
// console.log("editor in ReferenceList", editor);
|
||||
function ReferenceList({ editor, lng }: ReferenceListProps) {
|
||||
//i18n
|
||||
const { t } = useTranslation(lng);
|
||||
const [newTitle, setNewTitle] = useState("");
|
||||
const [newAuthor, setNewAuthor] = useState("");
|
||||
const [newYear, setNewYear] = useState("");
|
||||
|
@ -141,7 +145,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
|||
copyToClipboard(formatReferenceForCopy(reference))
|
||||
}
|
||||
>
|
||||
复制
|
||||
{t("复制")}
|
||||
</button>
|
||||
<ParagraphDeleteButton
|
||||
index={index}
|
||||
|
@ -180,35 +184,35 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
|||
type="text"
|
||||
value={newTitle}
|
||||
onChange={(e) => setNewTitle(e.target.value)}
|
||||
placeholder="Title"
|
||||
placeholder={t("Title")}
|
||||
/>
|
||||
<input
|
||||
className="border p-2 rounded"
|
||||
type="text"
|
||||
value={newAuthor}
|
||||
onChange={(e) => setNewAuthor(e.target.value)}
|
||||
placeholder="Author"
|
||||
placeholder={t("Author")}
|
||||
/>
|
||||
<input
|
||||
className="border p-2 rounded"
|
||||
type="text"
|
||||
value={newYear}
|
||||
onChange={(e) => setNewYear(e.target.value)}
|
||||
placeholder="Year"
|
||||
placeholder={t("Year")}
|
||||
/>
|
||||
<input
|
||||
className="border p-2 rounded"
|
||||
type="text"
|
||||
value={newPublisher}
|
||||
onChange={(e) => setNewPublisher(e.target.value)}
|
||||
placeholder="Publisher"
|
||||
placeholder={t("Publisher")}
|
||||
/>
|
||||
<input
|
||||
className="border p-2 rounded"
|
||||
type="text"
|
||||
value={newUrl}
|
||||
onChange={(e) => setNewUrl(e.target.value)}
|
||||
placeholder="URL"
|
||||
placeholder={t("Url")}
|
||||
/>
|
||||
</div>
|
||||
<div className="container mx-auto p-4">
|
||||
|
@ -218,7 +222,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
|||
type="submit"
|
||||
form="referenceForm"
|
||||
>
|
||||
添加自定义引用
|
||||
{t("添加自定义引用")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
@ -228,7 +232,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
|||
copyToClipboard(formatAllReferencesForCopy(references))
|
||||
}
|
||||
>
|
||||
复制所有引用
|
||||
{t("复制所有引用")}
|
||||
</button>
|
||||
<button
|
||||
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded "
|
||||
|
@ -236,7 +240,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
|||
// onClick={() => setReferences([])} // 设置引用列表为空数组
|
||||
onClick={() => handleClearReferences()}
|
||||
>
|
||||
删除所有引用
|
||||
{t("删除所有引用")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,32 +11,33 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
|
||||
import Link from "next/link";
|
||||
import { useLocalStorage } from "react-use";
|
||||
import { useTranslation } from "@/app/i18n/client";
|
||||
|
||||
// 在 Settings.tsx 或一个单独的配置文件中
|
||||
const CONFIG_OPTIONS = [
|
||||
{
|
||||
name: "cocopilot-gpt4(apiKey在前面手动加上ghu,因为GitHub不允许上传完整的密钥)",
|
||||
apiKey: "_pXVxLPBzcvCjSvG0Mv4K7G9ffw3xsM2ZKolZ",
|
||||
upstreamUrl: "https://proxy.cocopilot.org",
|
||||
},
|
||||
{
|
||||
name: "deepseek-chat(需要手动修改模型为这个)",
|
||||
apiKey: "sk-ffe19ebe9fa44d00884330ff1c18cf82",
|
||||
upstreamUrl: "https://api.deepseek.com",
|
||||
},
|
||||
{
|
||||
name: "caifree(推荐)",
|
||||
apiKey: "sk-aiHrrRLYUUelHstX69E9484509254dBf92061d6744FfFaD1",
|
||||
upstreamUrl: "https://one.caifree.com",
|
||||
},
|
||||
{
|
||||
name: "自定义",
|
||||
apiKey: "",
|
||||
upstreamUrl: "",
|
||||
},
|
||||
];
|
||||
|
||||
const Settings = () => {
|
||||
const Settings = ({ lng }: { lng: string }) => {
|
||||
//i18n
|
||||
const { t } = useTranslation(lng);
|
||||
const CONFIG_OPTIONS = [
|
||||
{
|
||||
name: t("configurations.cocopilot-gpt4"),
|
||||
apiKey: "_pXVxLPBzcvCjSvG0Mv4K7G9ffw3xsM2ZKolZ",
|
||||
upstreamUrl: "https://proxy.cocopilot.org",
|
||||
},
|
||||
{
|
||||
name: t("configurations.deepseek-chat"),
|
||||
apiKey: "sk-ffe19ebe9fa44d00884330ff1c18cf82",
|
||||
upstreamUrl: "https://api.deepseek.com",
|
||||
},
|
||||
{
|
||||
name: t("configurations.caifree"),
|
||||
apiKey: "sk-aiHrrRLYUUelHstX69E9484509254dBf92061d6744FfFaD1",
|
||||
upstreamUrl: "https://one.caifree.com",
|
||||
},
|
||||
{
|
||||
name: t("configurations.custom"),
|
||||
apiKey: "",
|
||||
upstreamUrl: "",
|
||||
},
|
||||
];
|
||||
//redux
|
||||
const dispatch = useAppDispatch();
|
||||
const apiKey = useAppSelector((state) => state.auth.apiKey);
|
||||
|
@ -65,7 +66,7 @@ const Settings = () => {
|
|||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="config-selector"
|
||||
>
|
||||
配置选择器
|
||||
{t("配置选择器")}
|
||||
</label>
|
||||
<select
|
||||
id="config-selector"
|
||||
|
@ -106,7 +107,7 @@ const Settings = () => {
|
|||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="upstream-url"
|
||||
>
|
||||
Upstream URL:
|
||||
{t("Upstream URL:")}
|
||||
</label>
|
||||
<input
|
||||
id="upstream-url"
|
||||
|
@ -122,7 +123,7 @@ const Settings = () => {
|
|||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="system-prompt"
|
||||
>
|
||||
System Prompt(Paper2AI):
|
||||
{t("System Prompt(Paper2AI):")}
|
||||
</label>
|
||||
<textarea
|
||||
id="system-prompt"
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import ReduxProvider from "@/app/store/ReduxProvider";
|
||||
import Settings from "@/components/Settings";
|
||||
|
||||
export default function SettingsWrapper() {
|
||||
export default function SettingsWrapper({ lng }) {
|
||||
return (
|
||||
<ReduxProvider>
|
||||
<Settings />
|
||||
<Settings lng={lng} />
|
||||
</ReduxProvider>
|
||||
);
|
||||
}
|
||||
|
|
8
dictionaries.js
Normal file
8
dictionaries.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import "server-only";
|
||||
|
||||
const dictionaries = {
|
||||
en: () => import("./dictionaries/en.json").then((module) => module.default),
|
||||
nl: () => import("./dictionaries/nl.json").then((module) => module.default),
|
||||
};
|
||||
|
||||
export const getDictionary = async (locale) => dictionaries[locale]();
|
5
dictionaries/en.json
Normal file
5
dictionaries/en.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"products": {
|
||||
"cart": "Toevoegen aan Winkelwagen"
|
||||
}
|
||||
}
|
5
dictionaries/zh-CN.json
Normal file
5
dictionaries/zh-CN.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"products": {
|
||||
"cart": "Add to Cart"
|
||||
}
|
||||
}
|
|
@ -1,17 +1,53 @@
|
|||
import { NextResponse, type NextRequest } from 'next/server'
|
||||
import { createClient } from '@/utils/supabase/middleware'
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { createClient } from "@/utils/supabase/middleware";
|
||||
import { match } from "@formatjs/intl-localematcher";
|
||||
import Negotiator from "negotiator";
|
||||
|
||||
let locales = ["en", "zh-CN"];
|
||||
|
||||
function getLocale(request: NextRequest) {
|
||||
// 从请求中获取`Accept-Language`头
|
||||
const headers = {
|
||||
"accept-language": request.headers.get("accept-language") || undefined,
|
||||
};
|
||||
|
||||
// 使用`Negotiator`根据`Accept-Language`头获取优先语言列表
|
||||
const languages = new Negotiator({ headers }).languages();
|
||||
|
||||
// 定义默认语言
|
||||
let defaultLocale = "en";
|
||||
|
||||
// 使用`match`函数匹配最合适的语言
|
||||
return match(languages, locales, defaultLocale);
|
||||
}
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
// Check if there is any supported locale in the pathname
|
||||
const { pathname } = request.nextUrl;
|
||||
const pathnameHasLocale = locales.some(
|
||||
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
||||
);
|
||||
|
||||
try {
|
||||
// This `try/catch` block is only here for the interactive tutorial.
|
||||
// Feel free to remove once you have Supabase connected.
|
||||
const { supabase, response } = createClient(request)
|
||||
const { supabase, response } = createClient(request);
|
||||
|
||||
// 如果URL中已经包含地区代码,则刷新会话
|
||||
// Refresh session if expired - required for Server Components
|
||||
// https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware
|
||||
await supabase.auth.getSession()
|
||||
|
||||
return response
|
||||
if (pathnameHasLocale) {
|
||||
await supabase.auth.getSession();
|
||||
return response;
|
||||
}
|
||||
// 如果没有地区代码,则重定向到包含首选地区的URL
|
||||
if (!pathnameHasLocale) {
|
||||
const locale = getLocale(request);
|
||||
request.nextUrl.pathname = `/${locale}${pathname}`;
|
||||
// e.g. incoming request is /products
|
||||
// The new URL is now /en-US/products
|
||||
return NextResponse.redirect(request.nextUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
// If you are here, a Supabase client could not be created!
|
||||
// This is likely because you have not set up environment variables.
|
||||
|
@ -20,7 +56,7 @@ export async function middleware(request: NextRequest) {
|
|||
request: {
|
||||
headers: request.headers,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +69,6 @@ export const config = {
|
|||
* - favicon.ico (favicon file)
|
||||
* Feel free to modify this pattern to include more paths.
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.png).*)',
|
||||
"/((?!_next/static|_next/image|favicon.png).*)",
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
133
package-lock.json
generated
133
package-lock.json
generated
|
@ -24,6 +24,8 @@
|
|||
"geist": "^1.0.0",
|
||||
"i": "^0.3.7",
|
||||
"i18next": "^23.8.2",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-resources-to-backend": "^1.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"negotiator": "^0.6.3",
|
||||
"next": "latest",
|
||||
|
@ -34,6 +36,7 @@
|
|||
"quill-to-word": "^1.3.0",
|
||||
"raw-body": "^2.5.2",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-quill": "^2.0.0",
|
||||
|
@ -814,6 +817,11 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
|
@ -825,6 +833,15 @@
|
|||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/is-hotkey": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
||||
|
@ -877,8 +894,7 @@
|
|||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||
},
|
||||
"node_modules/@types/quill": {
|
||||
"version": "1.3.10",
|
||||
|
@ -892,7 +908,6 @@
|
|||
"version": "18.2.48",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
|
||||
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
@ -938,8 +953,7 @@
|
|||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.3",
|
||||
|
@ -2047,6 +2061,22 @@
|
|||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz",
|
||||
"integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-resources-to-backend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.0.tgz",
|
||||
"integrity": "sha512-8f1l03s+QxDmCfpSXCh9V+AFcxAwIp0UaroWuyOx+hmmv8484GcELHs+lnu54FrNij8cDBEXvEwhzZoXsKcVpg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
@ -3033,6 +3063,19 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-cookie": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.0.2.tgz",
|
||||
"integrity": "sha512-UnW1rZw1VibRdTvV8Ksr0BKKZoajeUxYLE89sIygDeyQgtz6ik89RHOM+3kib36G9M7HxheORggPoLk5DxAK7Q==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.5",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"universal-cookie": "^7.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
@ -3911,6 +3954,23 @@
|
|||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"node_modules/universal-cookie": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.0.2.tgz",
|
||||
"integrity": "sha512-EC9PA+1nojhJtVnKW2Z7WYah01jgYJApqhX+Y8XU97TnFd7KaoxWTHiTZFtfpfV50jEF1L8V5p64ZxIx3Q67dg==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-cookie/node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
@ -4685,6 +4745,11 @@
|
|||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
|
@ -4696,6 +4761,15 @@
|
|||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/is-hotkey": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
||||
|
@ -4747,8 +4821,7 @@
|
|||
"@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||
},
|
||||
"@types/quill": {
|
||||
"version": "1.3.10",
|
||||
|
@ -4762,7 +4835,6 @@
|
|||
"version": "18.2.48",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
|
||||
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
@ -4810,8 +4882,7 @@
|
|||
"@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
|
||||
},
|
||||
"@types/use-sync-external-store": {
|
||||
"version": "0.0.3",
|
||||
|
@ -5601,6 +5672,22 @@
|
|||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"i18next-browser-languagedetector": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz",
|
||||
"integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"i18next-resources-to-backend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.0.tgz",
|
||||
"integrity": "sha512-8f1l03s+QxDmCfpSXCh9V+AFcxAwIp0UaroWuyOx+hmmv8484GcELHs+lnu54FrNij8cDBEXvEwhzZoXsKcVpg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
@ -6280,6 +6367,16 @@
|
|||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"react-cookie": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.0.2.tgz",
|
||||
"integrity": "sha512-UnW1rZw1VibRdTvV8Ksr0BKKZoajeUxYLE89sIygDeyQgtz6ik89RHOM+3kib36G9M7HxheORggPoLk5DxAK7Q==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.5",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"universal-cookie": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
@ -6927,6 +7024,22 @@
|
|||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"universal-cookie": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.0.2.tgz",
|
||||
"integrity": "sha512-EC9PA+1nojhJtVnKW2Z7WYah01jgYJApqhX+Y8XU97TnFd7KaoxWTHiTZFtfpfV50jEF1L8V5p64ZxIx3Q67dg==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
"geist": "^1.0.0",
|
||||
"i": "^0.3.7",
|
||||
"i18next": "^23.8.2",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-resources-to-backend": "^1.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"negotiator": "^0.6.3",
|
||||
"next": "latest",
|
||||
|
@ -33,6 +35,7 @@
|
|||
"quill-to-word": "^1.3.0",
|
||||
"raw-body": "^2.5.2",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-quill": "^2.0.0",
|
||||
|
|
20
utils/global.d.ts
vendored
20
utils/global.d.ts
vendored
|
@ -7,10 +7,16 @@ export type JournalInfo = {
|
|||
};
|
||||
|
||||
export type Reference = {
|
||||
title: string;
|
||||
author: string;
|
||||
year: number|string;
|
||||
url: string;
|
||||
venue?: string;
|
||||
journal?: JournalInfo;
|
||||
};
|
||||
title: string;
|
||||
author: string;
|
||||
year: number | string;
|
||||
url: string;
|
||||
venue?: string;
|
||||
journal?: JournalInfo;
|
||||
};
|
||||
|
||||
export interface IndexProps {
|
||||
params: {
|
||||
lng: string;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user