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 SEditor from "../components/SlateEditor";
|
||||||
import SettingsLink from "@/components/SettingsLink";
|
import SettingsLink from "@/components/SettingsLink";
|
||||||
import PaperManagementWrapper from "@/components/PaperManagementWrapper";
|
import PaperManagementWrapper from "@/components/PaperManagementWrapper";
|
||||||
|
//i18n
|
||||||
|
import { useTranslation } from "@/app/i18n";
|
||||||
|
import { IndexProps } from "@/utils/global";
|
||||||
|
|
||||||
// import Error from "@/app/global-error";
|
// 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 cookieStore = cookies();
|
||||||
|
|
||||||
const canInitSupabaseClient = () => {
|
const canInitSupabaseClient = () => {
|
||||||
|
@ -39,8 +44,8 @@ export default function Index() {
|
||||||
<SettingsLink />
|
<SettingsLink />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<PaperManagementWrapper />
|
<PaperManagementWrapper lng={lng} />
|
||||||
<QuillWrapper />
|
<QuillWrapper lng={lng} />
|
||||||
<footer className="w-full border-t border-t-foreground/10 p-8 flex justify-center text-center text-xs">
|
<footer className="w-full border-t border-t-foreground/10 p-8 flex justify-center text-center text-xs">
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
|
@ -49,7 +54,7 @@ export default function Index() {
|
||||||
className="font-bold hover:underline"
|
className="font-bold hover:underline"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
give me a star in GitHub
|
{t("give me a star in GitHub")}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</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",
|
"paperNumberRedux",
|
||||||
"contentUpdatedFromNetwork",
|
"contentUpdatedFromNetwork",
|
||||||
"isVip",
|
"isVip",
|
||||||
|
"language",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ export interface APIState {
|
||||||
paperNumberRedux: string;
|
paperNumberRedux: string;
|
||||||
contentUpdatedFromNetwork: boolean;
|
contentUpdatedFromNetwork: boolean;
|
||||||
isVip: boolean;
|
isVip: boolean;
|
||||||
|
language: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: APIState = {
|
const initialState: APIState = {
|
||||||
|
@ -11,6 +12,7 @@ const initialState: APIState = {
|
||||||
paperNumberRedux: "1", //默认得给个值
|
paperNumberRedux: "1", //默认得给个值
|
||||||
contentUpdatedFromNetwork: false,
|
contentUpdatedFromNetwork: false,
|
||||||
isVip: false,
|
isVip: false,
|
||||||
|
language: "en",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stateSlice = createSlice({
|
export const stateSlice = createSlice({
|
||||||
|
@ -35,6 +37,9 @@ export const stateSlice = createSlice({
|
||||||
setIsVip: (state, action: PayloadAction<boolean>) => {
|
setIsVip: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isVip = action.payload;
|
state.isVip = action.payload;
|
||||||
},
|
},
|
||||||
|
setLanguage: (state, action: PayloadAction<string>) => {
|
||||||
|
state.language = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,6 +49,7 @@ export const {
|
||||||
setPaperNumberRedux,
|
setPaperNumberRedux,
|
||||||
setContentUpdatedFromNetwork,
|
setContentUpdatedFromNetwork,
|
||||||
setIsVip,
|
setIsVip,
|
||||||
|
setLanguage,
|
||||||
} = stateSlice.actions;
|
} = stateSlice.actions;
|
||||||
|
|
||||||
export const stateReducer = stateSlice.reducer;
|
export const stateReducer = stateSlice.reducer;
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { sendGAEvent } from "@next/third-parties/google";
|
import { sendGAEvent } from "@next/third-parties/google";
|
||||||
|
//i18n
|
||||||
|
import { useTranslation } from "@/app/i18n/client";
|
||||||
// BuyVipButton 组件
|
// BuyVipButton 组件
|
||||||
function BuyVipButton() {
|
function BuyVipButton({ lng }: { lng: string }) {
|
||||||
|
//i18n
|
||||||
|
const { t } = useTranslation(lng);
|
||||||
// 这是购买VIP的目标URL
|
// 这是购买VIP的目标URL
|
||||||
const targetUrl = "https://store.paperai.life";
|
const targetUrl = "https://store.paperai.life";
|
||||||
return (
|
return (
|
||||||
|
@ -13,7 +16,9 @@ function BuyVipButton() {
|
||||||
sendGAEvent({ event: "buyVipButtonClicked", value: "buy vip" })
|
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>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,14 +24,18 @@ import {
|
||||||
} from "@/utils/supabase/supabaseutils";
|
} from "@/utils/supabase/supabaseutils";
|
||||||
//动画
|
//动画
|
||||||
import { CSSTransition } from "react-transition-group";
|
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";
|
import ParagraphDeleteButton from "@/components/ParagraphDeleteInterface";
|
||||||
//vip充值按钮
|
//vip充值按钮
|
||||||
import BuyVipButton from "@/components/BuyVipButton"; // 假设这是购买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
|
//supabase
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
//redux
|
//redux
|
||||||
|
@ -133,7 +137,10 @@ const PaperManagement = () => {
|
||||||
<>
|
<>
|
||||||
<div className="paper-management-container flex flex-col items-center space-y-4">
|
<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">
|
<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>
|
</div>
|
||||||
{isVip ? (
|
{isVip ? (
|
||||||
<div>
|
<div>
|
||||||
|
@ -141,10 +148,13 @@ const PaperManagement = () => {
|
||||||
onClick={handleAddPaperClick}
|
onClick={handleAddPaperClick}
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||||
>
|
>
|
||||||
+ Add Paper
|
{t("+ Add Paper")}
|
||||||
</button>
|
</button>
|
||||||
<div className="flex flex-col items-center space-y-2">
|
<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 ? (
|
{paperNumbers.length > 0 ? (
|
||||||
<ul className="list-disc">
|
<ul className="list-disc">
|
||||||
{[...paperNumbers]
|
{[...paperNumbers]
|
||||||
|
@ -188,7 +198,7 @@ const PaperManagement = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<BuyVipButton />
|
<BuyVipButton lng={lng} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import ReduxProvider from "@/app/store/ReduxProvider";
|
import ReduxProvider from "@/app/store/ReduxProvider";
|
||||||
import PaperManagement from "@/components/PaperManagement";
|
import PaperManagement from "@/components/PaperManagement";
|
||||||
|
|
||||||
export default function PaperManagementWrapper() {
|
export default function PaperManagementWrapper({ lng }) {
|
||||||
return (
|
return (
|
||||||
<ReduxProvider>
|
<ReduxProvider>
|
||||||
<PaperManagement />
|
<PaperManagement lng={lng} />
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ import {
|
||||||
} from "@/utils/supabase/supabaseutils";
|
} from "@/utils/supabase/supabaseutils";
|
||||||
//debounce
|
//debounce
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
|
//i18n
|
||||||
|
import { useTranslation } from "@/app/i18n/client";
|
||||||
|
|
||||||
const toolbarOptions = [
|
const toolbarOptions = [
|
||||||
["bold", "italic", "underline", "strike"], // 加粗、斜体、下划线和删除线
|
["bold", "italic", "underline", "strike"], // 加粗、斜体、下划线和删除线
|
||||||
|
@ -60,7 +62,10 @@ const toolbarOptions = [
|
||||||
["clean"], // 清除格式按钮
|
["clean"], // 清除格式按钮
|
||||||
];
|
];
|
||||||
|
|
||||||
const QEditor = () => {
|
const QEditor = ({ lng }) => {
|
||||||
|
//i18n
|
||||||
|
const { t } = useTranslation(lng);
|
||||||
|
|
||||||
//读取redux中的API key
|
//读取redux中的API key
|
||||||
const apiKey = useAppSelector((state: any) => state.auth.apiKey);
|
const apiKey = useAppSelector((state: any) => state.auth.apiKey);
|
||||||
const upsreamUrl = useAppSelector((state: any) => state.auth.upsreamUrl);
|
const upsreamUrl = useAppSelector((state: any) => state.auth.upsreamUrl);
|
||||||
|
@ -367,19 +372,21 @@ const QEditor = () => {
|
||||||
value={userInput}
|
value={userInput}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
className="textarea-focus-expand flex-grow shadow appearance-none border rounded py-2 px-3 mr-2 text-grey-darker"
|
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
|
<button
|
||||||
onClick={handleAIWrite}
|
onClick={handleAIWrite}
|
||||||
className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 mr-2 rounded"
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={() => paper2AI(userInput)}
|
onClick={() => paper2AI(userInput)}
|
||||||
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 mr-2 rounded"
|
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 mr-2 rounded"
|
||||||
>
|
>
|
||||||
Paper2AI
|
{t("Paper2AI")}
|
||||||
</button>
|
</button>
|
||||||
{/* 论文网站 */}
|
{/* 论文网站 */}
|
||||||
<select
|
<select
|
||||||
|
@ -405,13 +412,13 @@ const QEditor = () => {
|
||||||
onClick={() => formatTextInEditor(quill)} // 假设 updateIndex 是处理更新操作的函数
|
onClick={() => formatTextInEditor(quill)} // 假设 updateIndex 是处理更新操作的函数
|
||||||
className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded"
|
className="bg-gray-300 hover:bg-gray-400 text-black font-bold py-2 px-4 rounded"
|
||||||
>
|
>
|
||||||
更新索引
|
{t("更新索引")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div id="editor"></div>
|
<div id="editor"></div>
|
||||||
<ReferenceList editor={quill} />
|
<ReferenceList editor={quill} lng={lng} />
|
||||||
<ExportDocx editor={quill} />
|
<ExportDocx editor={quill} lng={lng} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import ReduxProvider from "@/app/store/ReduxProvider";
|
import ReduxProvider from "@/app/store/ReduxProvider";
|
||||||
import QEditor from "@/components/QuillEditor";
|
import QEditor from "@/components/QuillEditor";
|
||||||
|
|
||||||
export default function QuillWrapper() {
|
export default function QuillWrapper({ lng }) {
|
||||||
return (
|
return (
|
||||||
<ReduxProvider>
|
<ReduxProvider>
|
||||||
<QEditor />
|
<QEditor lng={lng} />
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,16 @@ import {
|
||||||
//supabase
|
//supabase
|
||||||
import { submitPaper } from "@/utils/supabase/supabaseutils";
|
import { submitPaper } from "@/utils/supabase/supabaseutils";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
|
//i18n
|
||||||
|
import { useTranslation } from "@/app/i18n/client";
|
||||||
type ReferenceListProps = {
|
type ReferenceListProps = {
|
||||||
editor: any;
|
editor: any;
|
||||||
|
lng: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ReferenceList({ editor }: ReferenceListProps) {
|
function ReferenceList({ editor, lng }: ReferenceListProps) {
|
||||||
// console.log("editor in ReferenceList", editor);
|
//i18n
|
||||||
|
const { t } = useTranslation(lng);
|
||||||
const [newTitle, setNewTitle] = useState("");
|
const [newTitle, setNewTitle] = useState("");
|
||||||
const [newAuthor, setNewAuthor] = useState("");
|
const [newAuthor, setNewAuthor] = useState("");
|
||||||
const [newYear, setNewYear] = useState("");
|
const [newYear, setNewYear] = useState("");
|
||||||
|
@ -141,7 +145,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
||||||
copyToClipboard(formatReferenceForCopy(reference))
|
copyToClipboard(formatReferenceForCopy(reference))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
复制
|
{t("复制")}
|
||||||
</button>
|
</button>
|
||||||
<ParagraphDeleteButton
|
<ParagraphDeleteButton
|
||||||
index={index}
|
index={index}
|
||||||
|
@ -180,35 +184,35 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
||||||
type="text"
|
type="text"
|
||||||
value={newTitle}
|
value={newTitle}
|
||||||
onChange={(e) => setNewTitle(e.target.value)}
|
onChange={(e) => setNewTitle(e.target.value)}
|
||||||
placeholder="Title"
|
placeholder={t("Title")}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="border p-2 rounded"
|
className="border p-2 rounded"
|
||||||
type="text"
|
type="text"
|
||||||
value={newAuthor}
|
value={newAuthor}
|
||||||
onChange={(e) => setNewAuthor(e.target.value)}
|
onChange={(e) => setNewAuthor(e.target.value)}
|
||||||
placeholder="Author"
|
placeholder={t("Author")}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="border p-2 rounded"
|
className="border p-2 rounded"
|
||||||
type="text"
|
type="text"
|
||||||
value={newYear}
|
value={newYear}
|
||||||
onChange={(e) => setNewYear(e.target.value)}
|
onChange={(e) => setNewYear(e.target.value)}
|
||||||
placeholder="Year"
|
placeholder={t("Year")}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="border p-2 rounded"
|
className="border p-2 rounded"
|
||||||
type="text"
|
type="text"
|
||||||
value={newPublisher}
|
value={newPublisher}
|
||||||
onChange={(e) => setNewPublisher(e.target.value)}
|
onChange={(e) => setNewPublisher(e.target.value)}
|
||||||
placeholder="Publisher"
|
placeholder={t("Publisher")}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="border p-2 rounded"
|
className="border p-2 rounded"
|
||||||
type="text"
|
type="text"
|
||||||
value={newUrl}
|
value={newUrl}
|
||||||
onChange={(e) => setNewUrl(e.target.value)}
|
onChange={(e) => setNewUrl(e.target.value)}
|
||||||
placeholder="URL"
|
placeholder={t("Url")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="container mx-auto p-4">
|
<div className="container mx-auto p-4">
|
||||||
|
@ -218,7 +222,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
||||||
type="submit"
|
type="submit"
|
||||||
form="referenceForm"
|
form="referenceForm"
|
||||||
>
|
>
|
||||||
添加自定义引用
|
{t("添加自定义引用")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -228,7 +232,7 @@ function ReferenceList({ editor }: ReferenceListProps) {
|
||||||
copyToClipboard(formatAllReferencesForCopy(references))
|
copyToClipboard(formatAllReferencesForCopy(references))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
复制所有引用
|
{t("复制所有引用")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded "
|
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={() => setReferences([])} // 设置引用列表为空数组
|
||||||
onClick={() => handleClearReferences()}
|
onClick={() => handleClearReferences()}
|
||||||
>
|
>
|
||||||
删除所有引用
|
{t("删除所有引用")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,32 +11,33 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useLocalStorage } from "react-use";
|
import { useLocalStorage } from "react-use";
|
||||||
|
import { useTranslation } from "@/app/i18n/client";
|
||||||
|
|
||||||
// 在 Settings.tsx 或一个单独的配置文件中
|
const Settings = ({ lng }: { lng: string }) => {
|
||||||
const CONFIG_OPTIONS = [
|
//i18n
|
||||||
{
|
const { t } = useTranslation(lng);
|
||||||
name: "cocopilot-gpt4(apiKey在前面手动加上ghu,因为GitHub不允许上传完整的密钥)",
|
const CONFIG_OPTIONS = [
|
||||||
apiKey: "_pXVxLPBzcvCjSvG0Mv4K7G9ffw3xsM2ZKolZ",
|
{
|
||||||
upstreamUrl: "https://proxy.cocopilot.org",
|
name: t("configurations.cocopilot-gpt4"),
|
||||||
},
|
apiKey: "_pXVxLPBzcvCjSvG0Mv4K7G9ffw3xsM2ZKolZ",
|
||||||
{
|
upstreamUrl: "https://proxy.cocopilot.org",
|
||||||
name: "deepseek-chat(需要手动修改模型为这个)",
|
},
|
||||||
apiKey: "sk-ffe19ebe9fa44d00884330ff1c18cf82",
|
{
|
||||||
upstreamUrl: "https://api.deepseek.com",
|
name: t("configurations.deepseek-chat"),
|
||||||
},
|
apiKey: "sk-ffe19ebe9fa44d00884330ff1c18cf82",
|
||||||
{
|
upstreamUrl: "https://api.deepseek.com",
|
||||||
name: "caifree(推荐)",
|
},
|
||||||
apiKey: "sk-aiHrrRLYUUelHstX69E9484509254dBf92061d6744FfFaD1",
|
{
|
||||||
upstreamUrl: "https://one.caifree.com",
|
name: t("configurations.caifree"),
|
||||||
},
|
apiKey: "sk-aiHrrRLYUUelHstX69E9484509254dBf92061d6744FfFaD1",
|
||||||
{
|
upstreamUrl: "https://one.caifree.com",
|
||||||
name: "自定义",
|
},
|
||||||
apiKey: "",
|
{
|
||||||
upstreamUrl: "",
|
name: t("configurations.custom"),
|
||||||
},
|
apiKey: "",
|
||||||
];
|
upstreamUrl: "",
|
||||||
|
},
|
||||||
const Settings = () => {
|
];
|
||||||
//redux
|
//redux
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const apiKey = useAppSelector((state) => state.auth.apiKey);
|
const apiKey = useAppSelector((state) => state.auth.apiKey);
|
||||||
|
@ -65,7 +66,7 @@ const Settings = () => {
|
||||||
className="block text-gray-700 text-sm font-bold mb-2"
|
className="block text-gray-700 text-sm font-bold mb-2"
|
||||||
htmlFor="config-selector"
|
htmlFor="config-selector"
|
||||||
>
|
>
|
||||||
配置选择器
|
{t("配置选择器")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="config-selector"
|
id="config-selector"
|
||||||
|
@ -106,7 +107,7 @@ const Settings = () => {
|
||||||
className="block text-gray-700 text-sm font-bold mb-2"
|
className="block text-gray-700 text-sm font-bold mb-2"
|
||||||
htmlFor="upstream-url"
|
htmlFor="upstream-url"
|
||||||
>
|
>
|
||||||
Upstream URL:
|
{t("Upstream URL:")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="upstream-url"
|
id="upstream-url"
|
||||||
|
@ -122,7 +123,7 @@ const Settings = () => {
|
||||||
className="block text-gray-700 text-sm font-bold mb-2"
|
className="block text-gray-700 text-sm font-bold mb-2"
|
||||||
htmlFor="system-prompt"
|
htmlFor="system-prompt"
|
||||||
>
|
>
|
||||||
System Prompt(Paper2AI):
|
{t("System Prompt(Paper2AI):")}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="system-prompt"
|
id="system-prompt"
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import ReduxProvider from "@/app/store/ReduxProvider";
|
import ReduxProvider from "@/app/store/ReduxProvider";
|
||||||
import Settings from "@/components/Settings";
|
import Settings from "@/components/Settings";
|
||||||
|
|
||||||
export default function SettingsWrapper() {
|
export default function SettingsWrapper({ lng }) {
|
||||||
return (
|
return (
|
||||||
<ReduxProvider>
|
<ReduxProvider>
|
||||||
<Settings />
|
<Settings lng={lng} />
|
||||||
</ReduxProvider>
|
</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 { NextResponse, type NextRequest } from "next/server";
|
||||||
import { createClient } from '@/utils/supabase/middleware'
|
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) {
|
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 {
|
try {
|
||||||
// This `try/catch` block is only here for the interactive tutorial.
|
// This `try/catch` block is only here for the interactive tutorial.
|
||||||
// Feel free to remove once you have Supabase connected.
|
// 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
|
// Refresh session if expired - required for Server Components
|
||||||
// https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware
|
// https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware
|
||||||
await supabase.auth.getSession()
|
if (pathnameHasLocale) {
|
||||||
|
await supabase.auth.getSession();
|
||||||
return response
|
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) {
|
} catch (e) {
|
||||||
// If you are here, a Supabase client could not be created!
|
// If you are here, a Supabase client could not be created!
|
||||||
// This is likely because you have not set up environment variables.
|
// This is likely because you have not set up environment variables.
|
||||||
|
@ -20,7 +56,7 @@ export async function middleware(request: NextRequest) {
|
||||||
request: {
|
request: {
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +69,6 @@ export const config = {
|
||||||
* - favicon.ico (favicon file)
|
* - favicon.ico (favicon file)
|
||||||
* Feel free to modify this pattern to include more paths.
|
* 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",
|
"geist": "^1.0.0",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"i18next": "^23.8.2",
|
"i18next": "^23.8.2",
|
||||||
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
|
"i18next-resources-to-backend": "^1.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"negotiator": "^0.6.3",
|
"negotiator": "^0.6.3",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
|
@ -34,6 +36,7 @@
|
||||||
"quill-to-word": "^1.3.0",
|
"quill-to-word": "^1.3.0",
|
||||||
"raw-body": "^2.5.2",
|
"raw-body": "^2.5.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-cookie": "^7.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.0.5",
|
"react-i18next": "^14.0.5",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
|
@ -814,6 +817,11 @@
|
||||||
"tslib": "^2.4.0"
|
"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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||||
|
@ -825,6 +833,15 @@
|
||||||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/is-hotkey": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
||||||
|
@ -877,8 +894,7 @@
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.11",
|
"version": "15.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/quill": {
|
"node_modules/@types/quill": {
|
||||||
"version": "1.3.10",
|
"version": "1.3.10",
|
||||||
|
@ -892,7 +908,6 @@
|
||||||
"version": "18.2.48",
|
"version": "18.2.48",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
|
||||||
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
|
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
|
||||||
"devOptional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
@ -938,8 +953,7 @@
|
||||||
"node_modules/@types/scheduler": {
|
"node_modules/@types/scheduler": {
|
||||||
"version": "0.16.8",
|
"version": "0.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/use-sync-external-store": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
|
@ -2047,6 +2061,22 @@
|
||||||
"@babel/runtime": "^7.23.2"
|
"@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": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
@ -3033,6 +3063,19 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
"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": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
@ -4685,6 +4745,11 @@
|
||||||
"tslib": "^2.4.0"
|
"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": {
|
"@types/estree": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||||
|
@ -4696,6 +4761,15 @@
|
||||||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||||
"dev": true
|
"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": {
|
"@types/is-hotkey": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
||||||
|
@ -4747,8 +4821,7 @@
|
||||||
"@types/prop-types": {
|
"@types/prop-types": {
|
||||||
"version": "15.7.11",
|
"version": "15.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"@types/quill": {
|
"@types/quill": {
|
||||||
"version": "1.3.10",
|
"version": "1.3.10",
|
||||||
|
@ -4762,7 +4835,6 @@
|
||||||
"version": "18.2.48",
|
"version": "18.2.48",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
|
||||||
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
|
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
|
||||||
"devOptional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
@ -4810,8 +4882,7 @@
|
||||||
"@types/scheduler": {
|
"@types/scheduler": {
|
||||||
"version": "0.16.8",
|
"version": "0.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"@types/use-sync-external-store": {
|
"@types/use-sync-external-store": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
|
@ -5601,6 +5672,22 @@
|
||||||
"@babel/runtime": "^7.23.2"
|
"@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": {
|
"iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
@ -6280,6 +6367,16 @@
|
||||||
"loose-envify": "^1.1.0"
|
"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": {
|
"react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
"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": {
|
"unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
"geist": "^1.0.0",
|
"geist": "^1.0.0",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"i18next": "^23.8.2",
|
"i18next": "^23.8.2",
|
||||||
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
|
"i18next-resources-to-backend": "^1.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"negotiator": "^0.6.3",
|
"negotiator": "^0.6.3",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
|
@ -33,6 +35,7 @@
|
||||||
"quill-to-word": "^1.3.0",
|
"quill-to-word": "^1.3.0",
|
||||||
"raw-body": "^2.5.2",
|
"raw-body": "^2.5.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-cookie": "^7.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.0.5",
|
"react-i18next": "^14.0.5",
|
||||||
"react-quill": "^2.0.0",
|
"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 = {
|
export type Reference = {
|
||||||
title: string;
|
title: string;
|
||||||
author: string;
|
author: string;
|
||||||
year: number|string;
|
year: number | string;
|
||||||
url: string;
|
url: string;
|
||||||
venue?: string;
|
venue?: string;
|
||||||
journal?: JournalInfo;
|
journal?: JournalInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IndexProps {
|
||||||
|
params: {
|
||||||
|
lng: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user