From 4fcc80719e1c0e069d1524c89d60fb79344e53b5 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Tue, 28 May 2024 01:39:37 +0530 Subject: [PATCH] feat(i18n): settings static content --- frontend/package-lock.json | 83 +++++++++++++++++++--- frontend/package.json | 3 +- frontend/src/App.tsx | 1 + frontend/src/conversation/Conversation.tsx | 2 +- frontend/src/locale/en.json | 39 ++++++++++ frontend/src/locale/es.json | 39 ++++++++++ frontend/src/locale/i18n.ts | 21 ++++++ frontend/src/modals/index.tsx | 4 +- frontend/src/settings/APIKeys.tsx | 15 ++-- frontend/src/settings/Documents.tsx | 18 +++-- frontend/src/settings/General.tsx | 57 +++++++++++---- frontend/src/settings/Prompts.tsx | 14 ++-- frontend/src/settings/index.tsx | 16 +++-- 13 files changed, 267 insertions(+), 45 deletions(-) create mode 100644 frontend/src/locale/en.json create mode 100644 frontend/src/locale/es.json create mode 100644 frontend/src/locale/i18n.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 98aedce..c39f8bf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@reduxjs/toolkit": "^1.9.2", "@vercel/analytics": "^0.1.10", + "i18next": "^23.11.5", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-i18next": "^14.1.2", "react-markdown": "^8.0.7", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", @@ -354,11 +356,12 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", + "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -1485,7 +1488,7 @@ "version": "18.0.10", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/react": "*" } @@ -4134,6 +4137,15 @@ "react-is": "^16.7.0" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/human-signals": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", @@ -4158,6 +4170,29 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "23.11.5", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz", + "integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -6678,6 +6713,28 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-i18next": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.2.tgz", + "integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6875,9 +6932,10 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -7923,6 +7981,15 @@ "vite": "^2.6.0 || 3 || 4 || 5" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5c6ef82..ebcbb3f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,10 +21,12 @@ "dependencies": { "@reduxjs/toolkit": "^1.9.2", "@vercel/analytics": "^0.1.10", + "i18next": "^23.11.5", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-i18next": "^14.1.2", "react-markdown": "^8.0.7", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", @@ -58,5 +60,4 @@ "vite": "^5.0.13", "vite-plugin-svgr": "^4.2.0" } - } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4a8ef6c..3083f1f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import { inject } from '@vercel/analytics'; import { useMediaQuery } from './hooks'; import { useState } from 'react'; import Setting from './settings'; +import './locale/i18n'; inject(); diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index 05c8638..218e853 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -14,7 +14,7 @@ import { import Send from './../assets/send.svg'; import SendDark from './../assets/send_dark.svg'; import Spinner from './../assets/spinner.svg'; -import SpinnerDark from './../assets/spinner-dark.svg' +import SpinnerDark from './../assets/spinner-dark.svg'; import { FEEDBACK, Query } from './conversationModels'; import { sendFeedback } from './conversationApi'; import ArrowDown from './../assets/arrow-down.svg'; diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json new file mode 100644 index 0000000..22a51a2 --- /dev/null +++ b/frontend/src/locale/en.json @@ -0,0 +1,39 @@ +{ + "language": "English", + "chat":"Chat", + "newChat":"New Chat", + "myPlan":"My Plan", + "about":"About", + "inputPlaceholder":"Type your message here...", + "tagline":"DocsGPT uses GenAI, please review critial information using sources.", + "sourceDocs":"Source Docs", + "settings":{ + "label":"Settings", + "general":{ + "label":"General", + "selectTheme":"Select Theme", + "light":"Light", + "dark":"Dark", + "selectLanguage":"Select Language", + "chunks":"Chunks processed per query", + "prompt":"Active Prompt", + "deleteAllLabel":"Delete all Conversation", + "deleteAllBtn":"Delete all", + "addNew":"Add New" + }, + "documents":{ + "label":"Documents", + "name":"Document Name", + "date":"Vector Date", + "type":"Type", + "tokenUsage":"Token Usage" + }, + "apiKeys":{ + "label":"API Keys", + "name":"Name", + "key":"API Key", + "sourceDoc":"Source Document", + "createNew":"Create New" + } + } +} diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json new file mode 100644 index 0000000..3d03bc3 --- /dev/null +++ b/frontend/src/locale/es.json @@ -0,0 +1,39 @@ +{ + "language": "Spanish", + "chat": "Chat", + "newChat": "Nuevo Chat", + "myPlan": "Mi Plan", + "about": "Acerca de", + "inputPlaceholder": "Escribe tu mensaje aquí...", + "tagline": "DocsGPT utiliza GenAI, por favor revisa información crítica utilizando fuentes.", + "sourceDocs": "Documentos Fuente", + "settings": { + "label": "Configuración", + "general": { + "label":"General", + "selectTheme": "Seleccionar Tema", + "light":"de luz", + "dark":"oscura", + "selectLanguage": "Seleccionar Idioma", + "chunks": "Trozos procesados por consulta", + "prompt": "Prompt Activo", + "deleteAllLabel": "Eliminar toda la Conversación", + "deleteAllBtn": "Eliminar todo", + "addNew": "Agregar Nuevo" + }, + "documents": { + "label":"Documentos", + "name": "Nombre del Documento", + "date": "Fecha Vector", + "type": "Tipo", + "tokenUsage": "Uso de Tokens" + }, + "apiKeys": { + "label":"Claves API", + "name": "Nombre", + "key": "Clave de API", + "sourceDoc": "Documento Fuente", + "createNew": "Crear Nuevo" + } + } +} diff --git a/frontend/src/locale/i18n.ts b/frontend/src/locale/i18n.ts new file mode 100644 index 0000000..6c170f8 --- /dev/null +++ b/frontend/src/locale/i18n.ts @@ -0,0 +1,21 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import en from './en.json'; //English +import es from './es.json'; //Spanish + +i18n.use(initReactI18next).init({ + resources: { + en: { + translation: en, + }, + es: { + translation: es, + }, + }, +}); + +const locale = localStorage.getItem('docsgpt-locale') ?? 'en'; +i18n.changeLanguage(locale); + +export default i18n; diff --git a/frontend/src/modals/index.tsx b/frontend/src/modals/index.tsx index a13d703..235162a 100644 --- a/frontend/src/modals/index.tsx +++ b/frontend/src/modals/index.tsx @@ -19,11 +19,11 @@ const Modal = (props: ModalProps) => { } absolute z-30 h-screen w-screen bg-gray-alpha`} > {props.render()} -
+
diff --git a/frontend/src/settings/APIKeys.tsx b/frontend/src/settings/APIKeys.tsx index b7a98c8..54a54dd 100644 --- a/frontend/src/settings/APIKeys.tsx +++ b/frontend/src/settings/APIKeys.tsx @@ -9,13 +9,14 @@ import { import { selectSourceDocs } from '../preferences/preferenceSlice'; import Exit from '../assets/exit.svg'; import Trash from '../assets/trash.svg'; - +import { useTranslation } from 'react-i18next'; const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; const embeddingsName = import.meta.env.VITE_EMBEDDINGS_NAME || 'huggingface_sentence-transformers/all-mpnet-base-v2'; const APIKeys: React.FC = () => { + const { t } = useTranslation(); const [isCreateModalOpen, setCreateModal] = React.useState(false); const [isSaveKeyModalOpen, setSaveKeyModal] = React.useState(false); const [newKey, setNewKey] = React.useState(''); @@ -97,7 +98,7 @@ const APIKeys: React.FC = () => { onClick={() => setCreateModal(true)} className="rounded-full bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]" > - Create new + {t('settings.apiKeys.createNew')}
{isCreateModalOpen && ( @@ -117,11 +118,15 @@ const APIKeys: React.FC = () => { - + + - diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index c7e7e83..e73ce73 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -1,10 +1,12 @@ import { DocumentsProps } from '../models/misc'; import Trash from '../assets/trash.svg'; import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; const Documents: React.FC = ({ documents, handleDeleteDocument, }) => { + const { t } = useTranslation(); return (
@@ -12,10 +14,18 @@ const Documents: React.FC = ({
Name + {t('settings.apiKeys.name')} + + {t('settings.apiKeys.sourceDoc')} + - Source document + {t('settings.apiKeys.key')} API Key
- - - - + + + + diff --git a/frontend/src/settings/General.tsx b/frontend/src/settings/General.tsx index 8801376..470b6d3 100644 --- a/frontend/src/settings/General.tsx +++ b/frontend/src/settings/General.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Prompts from './Prompts'; import { useDarkTheme } from '../hooks'; +import { useTranslation } from 'react-i18next'; import Dropdown from '../components/Dropdown'; import { selectPrompt, @@ -14,8 +15,22 @@ import { const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; const General: React.FC = () => { - const themes = ['Light', 'Dark']; - const languages = ['English']; + const { + t, + i18n: { changeLanguage, language }, + } = useTranslation(); + const themes = [t('settings.general.light'), t('settings.general.dark')]; + + const languageOptions = [ + { + label: 'English', + value: 'en', + }, + { + label: 'Spanish', + value: 'es', + }, + ]; const chunks = ['0', '2', '4', '6', '8', '10']; const [prompts, setPrompts] = React.useState< { name: string; id: string; type: string }[] @@ -23,12 +38,18 @@ const General: React.FC = () => { const selectedChunks = useSelector(selectChunks); const [isDarkTheme, toggleTheme] = useDarkTheme(); const [selectedTheme, setSelectedTheme] = React.useState( - isDarkTheme ? 'Dark' : 'Light', + isDarkTheme ? t('settings.general.dark') : t('settings.general.light'), ); const dispatch = useDispatch(); - const [selectedLanguage, setSelectedLanguage] = React.useState(languages[0]); + const locale = localStorage.getItem('docsgpt-locale'); + const [selectedLanguage, setSelectedLanguage] = React.useState( + locale ? languageOptions.find((option) => option.value === locale) : 'en', + ); const selectedPrompt = useSelector(selectPrompt); - + useEffect(() => { + console.log(selectedLanguage); + console.log(language); + }, [selectedLanguage]); React.useEffect(() => { const fetchPrompts = async () => { try { @@ -48,7 +69,9 @@ const General: React.FC = () => { return (
-

Select Theme

+

+ {t('settings.general.selectTheme')} +

{

- Select Language + {t('settings.general.selectLanguage')}

{ + setSelectedLanguage(selectedOption); + changeLanguage(selectedOption.value); + localStorage.setItem('docsgpt-locale', selectedOption.value); + }} size="w-56" rounded="3xl" border="border" @@ -76,7 +103,7 @@ const General: React.FC = () => {

- Chunks processed per query + {t('settings.general.chunks')}

{

- Delete all conversations + {t('settings.general.deleteAllLabel')}

diff --git a/frontend/src/settings/Prompts.tsx b/frontend/src/settings/Prompts.tsx index e5af189..2bae07e 100644 --- a/frontend/src/settings/Prompts.tsx +++ b/frontend/src/settings/Prompts.tsx @@ -2,9 +2,8 @@ import React from 'react'; import { PromptProps, ActiveState } from '../models/misc'; import Dropdown from '../components/Dropdown'; import PromptsModal from '../preferences/PromptsModal'; - +import { useTranslation } from 'react-i18next'; const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; - const Prompts: React.FC = ({ prompts, selectedPrompt, @@ -34,7 +33,10 @@ const Prompts: React.FC = ({ }); const [modalType, setModalType] = React.useState<'ADD' | 'EDIT'>('ADD'); const [modalState, setModalState] = React.useState('INACTIVE'); - + const { + t, + i18n: { changeLanguage, language }, + } = useTranslation(); const handleAddPrompt = async () => { try { const response = await fetch(`${apiHost}/api/create_prompt`, { @@ -158,7 +160,9 @@ const Prompts: React.FC = ({
-

Active Prompt

+

+ {t('settings.general.prompt')} +

= ({ setModalState('ACTIVE'); }} > - Add new + {t('settings.general.addNew')}
diff --git a/frontend/src/settings/index.tsx b/frontend/src/settings/index.tsx index 1c0fb82..9c0714c 100644 --- a/frontend/src/settings/index.tsx +++ b/frontend/src/settings/index.tsx @@ -11,12 +11,18 @@ import { import { Doc } from '../preferences/preferenceApi'; import ArrowLeft from '../assets/arrow-left.svg'; import ArrowRight from '../assets/arrow-right.svg'; +import { useTranslation } from 'react-i18next'; const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; const Settings: React.FC = () => { const dispatch = useDispatch(); - const tabs = ['General', 'Documents', 'API Keys']; + const { t } = useTranslation(); + const tabs = [ + t('settings.general.label'), + t('settings.documents.label'), + t('settings.apiKeys.label'), + ]; const [activeTab, setActiveTab] = React.useState('General'); const [widgetScreenshot, setWidgetScreenshot] = React.useState( null, @@ -45,7 +51,7 @@ const Settings: React.FC = () => { return (

- Settings + {t('settings.label')}

@@ -100,9 +106,9 @@ const Settings: React.FC = () => { function renderActiveTab() { switch (activeTab) { - case 'General': + case t('settings.general.label'): return ; - case 'Documents': + case t('settings.documents.label'): return ( { onWidgetScreenshotChange={updateWidgetScreenshot} // Add this line /> ); - case 'API Keys': + case t('settings.apiKeys.label'): return ; default: return null;
Document NameVector DateToken usageType + {t('settings.documents.name')} + + {t('settings.documents.date')} + + {t('settings.documents.tokenUsage')} + + {t('settings.documents.type')} +