Skip to content

Add i18n support and send message/reset chat data attributes #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ REQUIRED data attributes:

- `data-temperature` — Override the chat model temperature. This must be a valid value for your AnythingLLM LLM provider. If unset it will use the embeds attached workspace model temperature or the system setting.

**Language & Localization**

- `data-language` — Set the language for the chat interface. If not specified, it will default to English (en). Currently supported languages: en (English).

**Style Overrides**

- `data-chat-icon` — The chat bubble icon show when chat is closed. Options are `plus`, `chatBubble`, `support`, `search2`, `search`, `magic`.
Expand Down Expand Up @@ -97,6 +101,10 @@ REQUIRED data attributes:

- `data-default-messages` - A string of comma-separated messages you want to display to the user when the chat widget has no history. Example: `"How are you?, What is so interesting about this project?, Tell me a joke."`

- `data-send-message-text` — Override the placeholder text in the message input field. If not specified, it will use the translated text based on the selected language.

- `data-reset-chat-text` — Override the text shown on the reset chat button. If not specified, it will use the translated text based on the selected language.

**Behavior Overrides**

- `data-open-on-load` — Once loaded, open the chat as default. It can still be closed by the user. To enable set this attribute to `on`. All other values will be ignored.
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
"markdown-it": "^13.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"uuid": "^9.0.1"
"uuid": "^9.0.1",
"i18next": "^23.11.3",
"react-i18next": "^14.1.1",
"i18next-browser-languagedetector": "^7.2.1"
},
"devDependencies": {
"@rollup/plugin-image": "^3.0.3",
Expand Down
6 changes: 4 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Head from "@/components/Head";
import OpenButton from "@/components/OpenButton";
import ChatWindow from "./components/ChatWindow";
import { useEffect } from "react";
import { I18nextProvider } from "react-i18next";
import i18next from "@/i18n";

export default function App() {
const { isChatOpen, toggleOpenChat } = useOpenChat();
Expand All @@ -31,7 +33,7 @@ export default function App() {
const windowHeight = embedSettings.windowHeight ?? "700px";

return (
<>
<I18nextProvider i18n={i18next}>
<Head />
<div
id="anything-llm-embed-chat-container"
Expand Down Expand Up @@ -67,6 +69,6 @@ export default function App() {
/>
</div>
)}
</>
</I18nextProvider>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CircleNotch, PaperPlaneRight } from "@phosphor-icons/react";
import React, { useState, useRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { embedderSettings } from "@/main.jsx";

export default function PromptInput({
message,
Expand All @@ -8,6 +10,7 @@ export default function PromptInput({
inputDisabled,
buttonDisabled,
}) {
const { t } = useTranslation();
const formRef = useRef(null);
const textareaRef = useRef(null);
const [_, setFocused] = useState(false);
Expand Down Expand Up @@ -71,7 +74,10 @@ export default function PromptInput({
}}
value={message}
className="allm-font-sans allm-border-none allm-cursor-text allm-max-h-[100px] allm-text-[14px] allm-mx-2 allm-py-2 allm-w-full allm-text-black allm-bg-transparent placeholder:allm-text-slate-800/60 allm-resize-none active:allm-outline-none focus:allm-outline-none allm-flex-grow"
placeholder={"Send a message"}
placeholder={
embedderSettings.settings.sendMessageText ||
t("chat.send-message")
}
id="message-input"
/>
<button
Expand Down
6 changes: 5 additions & 1 deletion src/components/ResetChat/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import ChatService from "@/models/chatService";
import { useTranslation } from "react-i18next";
import { embedderSettings } from "@/main.jsx";

export default function ResetChat({ setChatHistory, settings, sessionId }) {
const { t } = useTranslation();

const handleChatReset = async () => {
await ChatService.resetEmbedChatSession(settings, sessionId);
setChatHistory([]);
Expand All @@ -13,7 +17,7 @@ export default function ResetChat({ setChatHistory, settings, sessionId }) {
className="hover:allm-cursor-pointer allm-border-none allm-text-sm allm-bg-transparent hover:allm-opacity-80 hover:allm-underline"
onClick={() => handleChatReset()}
>
Reset Chat
{embedderSettings.settings.resetChatText || t("chat.reset-chat")}
</button>
</div>
);
Expand Down
31 changes: 31 additions & 0 deletions src/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import { defaultNS, resources } from "./locales/resources.js";

export function initI18n(settings) {
const language = settings?.language || "en";

i18next
.use(initReactI18next)
.use(LanguageDetector)
.init({
fallbackLng: "en",
lng: language,
debug: import.meta.env.DEV,
defaultNS,
resources,
load: "languageOnly",
detection: {
order: ["querystring", "navigator"],
lookupQuerystring: "lng",
},
interpolation: {
escapeValue: false,
},
});

return i18next;
}

export default i18next;
8 changes: 8 additions & 0 deletions src/locales/en/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const TRANSLATIONS = {
chat: {
"send-message": "Send a message",
"reset-chat": "Reset Chat",
},
};

export default TRANSLATIONS;
8 changes: 8 additions & 0 deletions src/locales/fr/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const TRANSLATIONS = {
chat: {
"send-message": "Envoyer un message",
"reset-chat": "Réinitialiser la conversation",
},
};

export default TRANSLATIONS;
11 changes: 11 additions & 0 deletions src/locales/resources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import English from "./en/common.js";
import French from "./fr/common.js";
export const defaultNS = "common";
export const resources = {
en: {
common: English,
},
fr: {
common: French,
},
};
20 changes: 13 additions & 7 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { parseStylesSrc } from "./utils/constants.js";
const appElement = document.createElement("div");
import { initI18n } from "./i18n.js";

const appElement = document.createElement("div");
document.body.appendChild(appElement);
const root = ReactDOM.createRoot(appElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

const scriptSettings = Object.assign(
{},
document?.currentScript?.dataset || {}
);

export const embedderSettings = {
settings: scriptSettings,
stylesSrc: parseStylesSrc(document?.currentScript?.src),
Expand All @@ -29,3 +25,13 @@ export const embedderSettings = {
base: `allm-text-[#222628] allm-rounded-t-[18px] allm-rounded-br-[18px] allm-rounded-bl-[4px] allm-mr-[37px] allm-ml-[9px]`,
},
};

// Initialize i18n after settings are available
initI18n(scriptSettings);

const root = ReactDOM.createRoot(appElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
46 changes: 46 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"

"@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9":
version "7.27.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
dependencies:
regenerator-runtime "^0.14.0"

"@babel/template@^7.22.15", "@babel/template@^7.23.9":
version "7.23.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a"
Expand Down Expand Up @@ -1788,11 +1795,32 @@ highlight.js@^11.9.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==

html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"

human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==

i18next-browser-languagedetector@^7.2.1:
version "7.2.2"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.2.tgz#748e7dc192847613911d8a79d9d9a6c2d266133e"
integrity sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ==
dependencies:
"@babel/runtime" "^7.23.2"

i18next@^23.11.3:
version "23.16.8"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.16.8.tgz#3ae1373d344c2393f465556f394aba5a9233b93a"
integrity sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==
dependencies:
"@babel/runtime" "^7.23.2"

ignore-by-default@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
Expand Down Expand Up @@ -2694,6 +2722,14 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"

react-i18next@^14.1.1:
version "14.1.3"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.3.tgz#85525c4294ef870ddd3f5d184e793cae362f47cb"
integrity sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw==
dependencies:
"@babel/runtime" "^7.23.9"
html-parse-stringify "^3.0.1"

react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
Expand Down Expand Up @@ -2737,6 +2773,11 @@ reflect.getprototypeof@^1.0.4:
globalthis "^1.0.3"
which-builtin-type "^1.1.3"

regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==

regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
Expand Down Expand Up @@ -3327,6 +3368,11 @@ vite@^5.0.0:
optionalDependencies:
fsevents "~2.3.3"

void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==

which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
Expand Down