feat: Add i18n support for Chinese language (#142)
* feat: Add i18n support for Chinese language * fix: locale not working in Docker environment
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Conversation, KeyValuePair, Message, OpenAIModel } from "@/types";
|
||||
import { FC, MutableRefObject, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { ChatInput } from "./ChatInput";
|
||||
import { ChatLoader } from "./ChatLoader";
|
||||
import { ChatMessage } from "./ChatMessage";
|
||||
@@ -23,6 +24,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Chat: FC<Props> = ({ conversation, models, apiKey, serverSideApiKeyIsSet, messageIsStreaming, modelError, messageError, loading, lightMode, onSend, onUpdateConversation, onEditMessage, stopConversationRef }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const [currentMessage, setCurrentMessage] = useState<Message>();
|
||||
const [autoScrollEnabled, setAutoScrollEnabled] = useState(true);
|
||||
|
||||
@@ -71,14 +73,14 @@ export const Chat: FC<Props> = ({ conversation, models, apiKey, serverSideApiKey
|
||||
<div className="relative flex-1 overflow-none dark:bg-[#343541] bg-white">
|
||||
{!(apiKey || serverSideApiKeyIsSet) ? (
|
||||
<div className="flex flex-col justify-center mx-auto h-full w-[300px] sm:w-[500px] space-y-6">
|
||||
<div className="text-2xl font-semibold text-center text-gray-800 dark:text-gray-100">OpenAI API Key Required</div>
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">Please set your OpenAI API key in the bottom left of the sidebar.</div>
|
||||
<div className="text-2xl font-semibold text-center text-gray-800 dark:text-gray-100">{t('OpenAI API Key Required')}</div>
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">{t('Please set your OpenAI API key in the bottom left of the sidebar.')}</div>
|
||||
</div>
|
||||
) : modelError ? (
|
||||
<div className="flex flex-col justify-center mx-auto h-full w-[300px] sm:w-[500px] space-y-6">
|
||||
<div className="text-center text-red-500">Error fetching models.</div>
|
||||
<div className="text-center text-red-500">Make sure your OpenAI API key is set in the bottom left of the sidebar.</div>
|
||||
<div className="text-center text-red-500">If you completed this step, OpenAI may be experiencing issues.</div>
|
||||
<div className="text-center text-red-500">{t('Error fetching models.')}</div>
|
||||
<div className="text-center text-red-500">{t('Make sure your OpenAI API key is set in the bottom left of the sidebar.')}</div>
|
||||
<div className="text-center text-red-500">{t('If you completed this step, OpenAI may be experiencing issues.')}</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -89,7 +91,7 @@ export const Chat: FC<Props> = ({ conversation, models, apiKey, serverSideApiKey
|
||||
{conversation.messages.length === 0 ? (
|
||||
<>
|
||||
<div className="flex flex-col mx-auto pt-12 space-y-10 w-[350px] sm:w-[600px]">
|
||||
<div className="text-4xl font-semibold text-center text-gray-800 dark:text-gray-100">{models.length === 0 ? "Loading..." : "Chatbot UI"}</div>
|
||||
<div className="text-4xl font-semibold text-center text-gray-800 dark:text-gray-100">{models.length === 0 ? t("Loading...") : "Chatbot UI"}</div>
|
||||
|
||||
{models.length > 0 && (
|
||||
<div className="flex flex-col h-full space-y-4 border p-4 rounded border-neutral-500">
|
||||
@@ -109,7 +111,7 @@ export const Chat: FC<Props> = ({ conversation, models, apiKey, serverSideApiKey
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center py-2 text-neutral-500 bg-neutral-100 dark:bg-[#444654] dark:text-neutral-200 text-sm border border-b-neutral-300 dark:border-none">Model: {conversation.model.name}</div>
|
||||
<div className="flex justify-center py-2 text-neutral-500 bg-neutral-100 dark:bg-[#444654] dark:text-neutral-200 text-sm border border-b-neutral-300 dark:border-none">{t('Model')}: {conversation.model.name}</div>
|
||||
|
||||
{conversation.messages.map((message, index) => (
|
||||
<ChatMessage
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Message, OpenAIModel, OpenAIModelID } from "@/types";
|
||||
import { IconPlayerStop, IconRepeat, IconSend } from "@tabler/icons-react";
|
||||
import { FC, KeyboardEvent, MutableRefObject, useEffect, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
interface Props {
|
||||
messageIsStreaming: boolean;
|
||||
@@ -13,6 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ChatInput: FC<Props> = ({ messageIsStreaming, model, messages, onSend, onRegenerate, stopConversationRef, textareaRef }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const [content, setContent] = useState<string>();
|
||||
const [isTyping, setIsTyping] = useState<boolean>(false);
|
||||
|
||||
@@ -21,7 +23,7 @@ export const ChatInput: FC<Props> = ({ messageIsStreaming, model, messages, onSe
|
||||
const maxLength = model.id === OpenAIModelID.GPT_3_5 ? 12000 : 24000;
|
||||
|
||||
if (value.length > maxLength) {
|
||||
alert(`Message limit is ${maxLength} characters. You have entered ${value.length} characters.`);
|
||||
alert(t(`Message limit is {{maxLength}} characters. You have entered {{valueLength}} characters.`, { maxLength, valueLength: value.length }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ export const ChatInput: FC<Props> = ({ messageIsStreaming, model, messages, onSe
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
alert("Please enter a message");
|
||||
alert(t("Please enter a message"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ export const ChatInput: FC<Props> = ({ messageIsStreaming, model, messages, onSe
|
||||
size={16}
|
||||
className="inline-block mb-[2px]"
|
||||
/>{" "}
|
||||
Stop Generating
|
||||
{t('Stop Generating')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -115,7 +117,7 @@ export const ChatInput: FC<Props> = ({ messageIsStreaming, model, messages, onSe
|
||||
maxHeight: "400px",
|
||||
overflow: `${textareaRef.current && textareaRef.current.scrollHeight > 400 ? "auto" : "hidden"}`
|
||||
}}
|
||||
placeholder="Type a message..."
|
||||
placeholder={t("Type a message...") || ''}
|
||||
value={content}
|
||||
rows={1}
|
||||
onCompositionStart={() => setIsTyping(true)}
|
||||
@@ -144,7 +146,7 @@ export const ChatInput: FC<Props> = ({ messageIsStreaming, model, messages, onSe
|
||||
>
|
||||
ChatBot UI
|
||||
</a>
|
||||
. Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.
|
||||
. {t("Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Message } from "@/types";
|
||||
import { IconEdit } from "@tabler/icons-react";
|
||||
import { FC, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { CodeBlock } from "../Markdown/CodeBlock";
|
||||
@@ -13,6 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ChatMessage: FC<Props> = ({ message, messageIndex, lightMode, onEditMessage }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||
const [isHovering, setIsHovering] = useState<boolean>(false);
|
||||
const [messageContent, setMessageContent] = useState(message.content);
|
||||
@@ -60,7 +62,7 @@ export const ChatMessage: FC<Props> = ({ message, messageIndex, lightMode, onEdi
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<div className="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto relative">
|
||||
<div className="font-bold min-w-[40px]">{message.role === "assistant" ? "AI:" : "You:"}</div>
|
||||
<div className="font-bold min-w-[40px]">{message.role === "assistant" ? t("AI") : t("You")}:</div>
|
||||
|
||||
<div className="prose dark:prose-invert mt-[-2px] w-full">
|
||||
{message.role === "user" ? (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { OpenAIModel } from "@/types";
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
interface Props {
|
||||
model: OpenAIModel;
|
||||
@@ -8,12 +9,13 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ModelSelect: FC<Props> = ({ model, models, onModelChange }) => {
|
||||
const {t} = useTranslation('chat')
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<label className="text-left mb-2 dark:text-neutral-400 text-neutral-700">Model</label>
|
||||
<label className="text-left mb-2 dark:text-neutral-400 text-neutral-700">{t('Model')}</label>
|
||||
<select
|
||||
className="w-full p-3 dark:text-white dark:bg-[#343541] border border-neutral-500 rounded-lg appearance-none focus:shadow-outline text-neutral-900 cursor-pointer"
|
||||
placeholder="Select a model"
|
||||
placeholder={t("Select a model") || ''}
|
||||
value={model.id}
|
||||
onChange={(e) => {
|
||||
onModelChange(models.find((model) => model.id === e.target.value) as OpenAIModel);
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { IconRefresh } from "@tabler/icons-react";
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
interface Props {
|
||||
onRegenerate: () => void;
|
||||
}
|
||||
|
||||
export const Regenerate: FC<Props> = ({ onRegenerate }) => {
|
||||
const { t } = useTranslation('chat')
|
||||
return (
|
||||
<div className="fixed sm:absolute bottom-4 sm:bottom-8 w-full sm:w-1/2 px-2 left-0 sm:left-[280px] lg:left-[200px] right-0 ml-auto mr-auto">
|
||||
<div className="text-center mb-4 text-red-500">Sorry, there was an error.</div>
|
||||
<div className="text-center mb-4 text-red-500">{t('Sorry, there was an error.')}</div>
|
||||
<button
|
||||
className="flex items-center justify-center w-full h-12 bg-neutral-100 dark:bg-[#444654] text-neutral-500 dark:text-neutral-200 text-sm font-semibold rounded-lg border border-b-neutral-300 dark:border-none"
|
||||
onClick={onRegenerate}
|
||||
>
|
||||
<IconRefresh className="mr-2" />
|
||||
<div>Regenerate response</div>
|
||||
<div>{t('Regenerate response')}</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Conversation } from "@/types";
|
||||
import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const";
|
||||
import { FC, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
interface Props {
|
||||
conversation: Conversation;
|
||||
@@ -8,6 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const SystemPrompt: FC<Props> = ({ conversation, onChangePrompt }) => {
|
||||
const { t } = useTranslation('chat')
|
||||
const [value, setValue] = useState<string>("");
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
@@ -17,7 +19,7 @@ export const SystemPrompt: FC<Props> = ({ conversation, onChangePrompt }) => {
|
||||
const maxLength = 4000;
|
||||
|
||||
if (value.length > maxLength) {
|
||||
alert(`Prompt limit is ${maxLength} characters`);
|
||||
alert(t(`Prompt limit is {{maxLength}} characters`, { maxLength }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,7 +47,7 @@ export const SystemPrompt: FC<Props> = ({ conversation, onChangePrompt }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<label className="text-left dark:text-neutral-400 text-neutral-700 mb-2">System Prompt</label>
|
||||
<label className="text-left dark:text-neutral-400 text-neutral-700 mb-2">{t('System Prompt')}</label>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className="w-full rounded-lg px-4 py-2 focus:outline-none dark:bg-[#40414F] dark:border-opacity-50 dark:border-neutral-800 dark:text-neutral-100 border border-neutral-500 shadow text-neutral-900"
|
||||
@@ -55,8 +57,8 @@ export const SystemPrompt: FC<Props> = ({ conversation, onChangePrompt }) => {
|
||||
maxHeight: "300px",
|
||||
overflow: `${textareaRef.current && textareaRef.current.scrollHeight > 400 ? "auto" : "hidden"}`
|
||||
}}
|
||||
placeholder="Enter a prompt"
|
||||
value={value}
|
||||
placeholder={t("Enter a prompt") || ''}
|
||||
value={t(value) || ''}
|
||||
rows={1}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user