From 95fffd274b528ead54bc663bc50357bbdbaec93e Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Sat, 13 Jan 2024 14:05:34 -0300 Subject: [PATCH 01/14] feature: i18n init for internalization --- frontend/src/components/i18n/Translator.ts | 13 ++ frontend/src/components/i18n/index.ts | 1 + frontend/src/i18n/index.ts | 14 ++ frontend/src/i18n/locales/en-us.ts | 161 +++++++++++++++++++++ frontend/src/i18n/locales/index.ts | 7 + frontend/src/i18n/locales/pt-br.ts | 161 +++++++++++++++++++++ 6 files changed, 357 insertions(+) create mode 100644 frontend/src/components/i18n/Translator.ts create mode 100644 frontend/src/components/i18n/index.ts create mode 100644 frontend/src/i18n/index.ts create mode 100644 frontend/src/i18n/locales/en-us.ts create mode 100644 frontend/src/i18n/locales/index.ts create mode 100644 frontend/src/i18n/locales/pt-br.ts diff --git a/frontend/src/components/i18n/Translator.ts b/frontend/src/components/i18n/Translator.ts new file mode 100644 index 0000000000..f4816fd7ca --- /dev/null +++ b/frontend/src/components/i18n/Translator.ts @@ -0,0 +1,13 @@ +import { useTranslation } from 'react-i18next'; + +type TranslatorProps = { + path: string; +}; + +const Translator = ({ path }: TranslatorProps) => { + const { t } = useTranslation(); + + return t(path); +}; + +export default Translator; diff --git a/frontend/src/components/i18n/index.ts b/frontend/src/components/i18n/index.ts new file mode 100644 index 0000000000..99be497cd8 --- /dev/null +++ b/frontend/src/components/i18n/index.ts @@ -0,0 +1 @@ +export { default as Translator } from './Translator'; diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts new file mode 100644 index 0000000000..dc62d2623c --- /dev/null +++ b/frontend/src/i18n/index.ts @@ -0,0 +1,14 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import translations from './locales'; + +const i18nConfig = { + resources: translations, + fallbackLng: 'en-US', + defaultNS: 'translations' +}; + +i18n.use(initReactI18next).init(i18nConfig); + +export default i18n; diff --git a/frontend/src/i18n/locales/en-us.ts b/frontend/src/i18n/locales/en-us.ts new file mode 100644 index 0000000000..07b6f4bc7c --- /dev/null +++ b/frontend/src/i18n/locales/en-us.ts @@ -0,0 +1,161 @@ +export default { + translations: { + components: { + atoms: { + buttons: { + userButton: { + menu: { + settings: 'Settings', + settingsKey: 'S', + APIKeys: 'API Keys', + logout: 'Logout' + } + } + } + }, + molecules: { + newChatButton: { + newChat: 'New Chat' + }, + tasklist: { + TaskList: { + title: '🗒️ Task List', + loading: 'Loading...', + error: 'An error occured' + } + }, + attachments: { + cancelUpload: 'Cancel upload', + removeAttachment: 'Remove attachment' + }, + newChatDialog: { + clearChat: + 'This will clear the current messages and start a new chat.', + cancel: 'Cancel', + confirm: 'Confirm' + }, + settingsModal: { + expandMessages: 'Expand Messages', + hideChainOfThought: 'Hide Chain of Thought', + darkMode: 'Dark Mode' + } + }, + organisms: { + chat: { + history: { + index: { + lastInputs: 'Last Inputs', + noInputs: 'Such empty...', + loading: 'Loading...' + } + }, + inputBox: { + input: { + placeholder: 'Type your message here...' + }, + speechButton: { + start: 'Start recording', + stop: 'Stop recording' + }, + SubmitButton: { + sendMessage: 'Send message', + stopTask: 'Stop Task' + }, + UploadButton: { + attachFiles: 'Attach files' + }, + waterMark: { + buildWith: 'Build with' + } + }, + Messages: { + index: { + running: 'Running', + executedSuccessfully: 'executed successfully', + failed: 'failed', + feedbackUpdated: 'Feedback updated', + updating: 'Updating' + } + }, + dropScreen: { + dropYourFilesHere: 'Drop your files here' + }, + index: { + failedToUpload: 'Failed to upload', + cancelledUploadOf: 'Cancelled upload of', + couldNotReachServer: 'Could not reach the server' + }, + settings: { + settingsPanel: 'Settings panel', + reset: 'Reset', + cancel: 'Cancel', + confirm: 'Confirm' + } + }, + threadHistory: { + sidebar: { + filters: { + FeedbackSelect: { + feedbackAll: 'Feedback: All', + feedbackPositive: 'Feedback: Positive', + feedbackNegative: 'Feedback: Negative' + }, + SearchBar: { + search: 'Search' + } + }, + DeleteThreadButton: { + confirmMessage: + "This will delete the thread as well as it's messages and elements.", + cancel: 'Cancel', + confirm: 'Confirm', + deletingChat: 'Deleting chat', + chatDeleted: 'Chat deleted' + }, + index: { + pastChats: 'Past Chats' + }, + ThreadList: { + empty: 'Empty...' + }, + TriggerButton: { + closeSidebar: 'Close sidebar', + openSidebar: 'Open sidebar' + } + }, + Thread: { + backToChat: 'Go back to chat', + chatCreatedOn: 'This chat was created on' + } + }, + header: { + chat: 'Chat', + readme: 'Readme' + } + } + }, + hooks: { + useLLMProviders: { + failedToFetchProviders: 'Failed to fetch providers:' + } + }, + pages: { + Design: {}, + Env: { + savedSuccessfully: 'Saved successfully', + requiredApiKeys: 'Required API Keys', + requiredApiKeysInfo: + "To use this app, the following API keys are required. The keys are stored on your device's local storage." + }, + Login: { + authTitle: 'Login to access the app.' + }, + Page: { + notPartOfProject: 'You are not part of this project.' + }, + ResumeButton: { + resumeChat: 'Resume Chat' + } + } + } +}; diff --git a/frontend/src/i18n/locales/index.ts b/frontend/src/i18n/locales/index.ts new file mode 100644 index 0000000000..f920071b75 --- /dev/null +++ b/frontend/src/i18n/locales/index.ts @@ -0,0 +1,7 @@ +import enUsTranslations from './en-us'; +import ptBrTranslations from './pt-br'; + +export default { + 'en-US': enUsTranslations, + 'pt-BR': ptBrTranslations +}; diff --git a/frontend/src/i18n/locales/pt-br.ts b/frontend/src/i18n/locales/pt-br.ts new file mode 100644 index 0000000000..b9226452c5 --- /dev/null +++ b/frontend/src/i18n/locales/pt-br.ts @@ -0,0 +1,161 @@ +export default { + translations: { + components: { + atoms: { + buttons: { + userButton: { + menu: { + settings: 'Configurações', + settingsKey: 'S', + APIKeys: 'Chaves de API', + logout: 'Sair' + } + } + } + }, + molecules: { + newChatButton: { + newChat: 'Nova Conversa' + }, + tasklist: { + TaskList: { + title: '🗒️ Lista de Tarefas', + loading: 'Carregando...', + error: 'Ocorreu um erro' + } + }, + attachments: { + cancelUpload: 'Cancelar upload', + removeAttachment: 'Remover anexo' + }, + newChatDialog: { + clearChat: + 'Isso limpará as mensagens atuais e iniciará uma nova conversa.', + cancel: 'Cancelar', + confirm: 'Confirmar' + }, + settingsModal: { + expandMessages: 'Expandir Mensagens', + hideChainOfThought: 'Esconder Modo de Pensamento', + darkMode: 'Modo Escuro' + } + }, + organisms: { + chat: { + history: { + index: { + lastInputs: 'Últimas Entradas', + noInputs: 'Vazio...', + loading: 'Carregando...' + } + }, + inputBox: { + input: { + placeholder: 'Digite sua mensagem aqui...' + }, + speechButton: { + start: 'Iniciar gravação', + stop: 'Parar gravação' + }, + SubmitButton: { + sendMessage: 'Enviar mensagem', + stopTask: 'Parar Tarefa' + }, + UploadButton: { + attachFiles: 'Anexar arquivos' + }, + waterMark: { + buildWith: 'Construído com' + } + }, + Messages: { + index: { + running: 'Executando', + executedSuccessfully: 'executado com sucesso', + failed: 'falhou', + feedbackUpdated: 'Feedback atualizado', + updating: 'Atualizando' + } + }, + dropScreen: { + dropYourFilesHere: 'Arraste seus arquivos aqui' + }, + index: { + failedToUpload: 'Falha ao enviar', + cancelledUploadOf: 'Envio cancelado de', + couldNotReachServer: 'Não foi possível conectar ao servidor' + }, + settings: { + settingsPanel: 'Painel de Configurações', + reset: 'Redefinir', + cancel: 'Cancelar', + confirm: 'Confirmar' + } + }, + threadHistory: { + sidebar: { + filters: { + FeedbackSelect: { + feedbackAll: 'Feedback: Todos', + feedbackPositive: 'Feedback: Positivo', + feedbackNegative: 'Feedback: Negativo' + }, + SearchBar: { + search: 'Pesquisar' + } + }, + DeleteThreadButton: { + confirmMessage: + 'Isso deletará o tópico, bem como suas mensagens e elementos.', + cancel: 'Cancelar', + confirm: 'Confirmar', + deletingChat: 'Excluindo conversa', + chatDeleted: 'Conversa excluída' + }, + index: { + pastChats: 'Conversas Anteriores' + }, + ThreadList: { + empty: 'Vazio...' + }, + TriggerButton: { + closeSidebar: 'Fechar barra lateral', + openSidebar: 'Abrir barra lateral' + } + }, + Thread: { + backToChat: 'Voltar para a conversa', + chatCreatedOn: 'Esta conversa foi criada em' + } + }, + header: { + chat: 'Conversa', + readme: 'Sobre' + } + } + }, + hooks: { + useLLMProviders: { + failedToFetchProviders: 'Falha ao buscar provedores:' + } + }, + pages: { + Design: {}, + Env: { + savedSuccessfully: 'Salvo com sucesso', + requiredApiKeys: 'Chaves de API necessárias', + requiredApiKeysInfo: + 'Para usar este aplicativo, as seguintes chaves de API são necessárias. As chaves são armazenadas no armazenamento local do seu dispositivo.' + }, + Login: { + authTitle: 'Faça login para acessar o aplicativo.' + }, + Page: { + notPartOfProject: 'Você não faz parte deste projeto.' + }, + ResumeButton: { + resumeChat: 'Continuar Conversa' + } + } + } +}; From 4f4f2f018deda55b4163b4cac7e89405ea04d86f Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Sat, 13 Jan 2024 14:07:13 -0300 Subject: [PATCH 02/14] feature: updating texts with nre internalization format --- frontend/package.json | 4 +- frontend/pnpm-lock.yaml | 43 +++++++++++++++++++ frontend/src/AppWrapper.tsx | 13 +++++- .../atoms/buttons/userButton/menu.tsx | 12 ++++-- .../src/components/molecules/attachments.tsx | 13 +++++- .../components/molecules/newChatButton.tsx | 4 +- .../components/molecules/newChatDialog.tsx | 10 +++-- .../components/molecules/settingsModal.tsx | 23 ++++++++-- .../molecules/tasklist/TaskList.tsx | 16 +++++-- .../organisms/chat/Messages/index.tsx | 19 +++++--- .../components/organisms/chat/dropScreen.tsx | 4 +- .../organisms/chat/history/index.tsx | 8 ++-- .../src/components/organisms/chat/index.tsx | 18 ++++++-- .../organisms/chat/inputBox/SubmitButton.tsx | 14 +++++- .../organisms/chat/inputBox/UploadButton.tsx | 8 +++- .../organisms/chat/inputBox/input.tsx | 7 ++- .../organisms/chat/inputBox/speechButton.tsx | 14 +++++- .../organisms/chat/inputBox/waterMark.tsx | 4 +- .../components/organisms/chat/settings.tsx | 14 ++++-- frontend/src/components/organisms/header.tsx | 10 ++++- .../organisms/threadHistory/Thread.tsx | 5 ++- .../sidebar/DeleteThreadButton.tsx | 16 ++++--- .../threadHistory/sidebar/ThreadList.tsx | 4 +- .../threadHistory/sidebar/TriggerButton.tsx | 17 +++++++- .../sidebar/filters/FeedbackSelect.tsx | 24 +++++++++-- .../sidebar/filters/SearchBar.tsx | 7 ++- .../organisms/threadHistory/sidebar/index.tsx | 4 +- frontend/src/hooks/useLLMProviders.ts | 7 ++- frontend/src/main.tsx | 2 + frontend/src/pages/Env.tsx | 11 +++-- frontend/src/pages/Login.tsx | 5 ++- frontend/src/pages/Page.tsx | 5 ++- frontend/src/pages/ResumeButton.tsx | 3 +- frontend/src/state/project.ts | 1 + frontend/src/state/settings.ts | 4 ++ 35 files changed, 307 insertions(+), 66 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 766448d998..09dbdb8cad 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,11 +20,13 @@ "@mui/lab": "^5.0.0-alpha.122", "@mui/material": "^5.14.10", "formik": "^2.4.3", + "i18next": "^23.7.16", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "sonner": "^1.2.3", "react-hotkeys-hook": "^4.4.1", + "react-i18next": "^14.0.0", "react-router-dom": "^6.15.0", "react-speech-recognition": "^3.10.0", "recoil": "^0.7.7", @@ -44,4 +46,4 @@ "vite": "^4.4.9", "vite-tsconfig-paths": "^4.2.0" } -} +} \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 1530040dc8..3ba4e695fd 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: formik: specifier: ^2.4.3 version: 2.4.3(react@18.2.0) + i18next: + specifier: ^23.7.16 + version: 23.7.16 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -41,6 +44,9 @@ dependencies: react-hotkeys-hook: specifier: ^4.4.1 version: 4.4.1(react-dom@18.2.0)(react@18.2.0) + react-i18next: + specifier: ^14.0.0 + version: 14.0.0(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0) react-router-dom: specifier: ^6.15.0 version: 6.15.0(react-dom@18.2.0)(react@18.2.0) @@ -1098,6 +1104,18 @@ packages: react-is: 16.13.1 dev: false + /html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + + /i18next@23.7.16: + resolution: {integrity: sha512-SrqFkMn9W6Wb43ZJ9qrO6U2U4S80RsFMA7VYFSqp7oc7RllQOYDCdRfsse6A7Cq/V8MnpxKvJCYgM8++27n4Fw==} + dependencies: + '@babel/runtime': 7.23.5 + dev: false + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1233,6 +1251,26 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-i18next@14.0.0(i18next@23.7.16)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-OCrS8rHNAmnr8ggGRDxjakzihrMW7HCbsplduTm3EuuQ6fyvWGT41ksZpqbduYoqJurBmEsEVZ1pILSUWkHZng==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.23.5 + html-parse-stringify: 3.0.1 + i18next: 23.7.16 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false @@ -1492,6 +1530,11 @@ packages: fsevents: 2.3.3 dev: true + /void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} diff --git a/frontend/src/AppWrapper.tsx b/frontend/src/AppWrapper.tsx index 1baf68ec0f..5d863d9f69 100644 --- a/frontend/src/AppWrapper.tsx +++ b/frontend/src/AppWrapper.tsx @@ -2,12 +2,13 @@ import App from 'App'; import { apiClient } from 'api'; import { useAuth } from 'api/auth'; import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRecoilState, useSetRecoilState } from 'recoil'; import { useApi } from '@chainlit/react-client'; import { IProjectSettings, projectSettingsState } from 'state/project'; -import { settingsState } from 'state/settings'; +import { Language, settingsState } from 'state/settings'; export default function AppWrapper() { const [projectSettings, setProjectSettings] = @@ -15,6 +16,12 @@ export default function AppWrapper() { const setAppSettings = useSetRecoilState(settingsState); const { isAuthenticated, isReady } = useAuth(); + const { i18n } = useTranslation(); + + function handleChangeLanguage(language: string): void { + i18n.changeLanguage(language); + } + const { data } = useApi( apiClient, projectSettings === undefined && isAuthenticated @@ -38,8 +45,10 @@ export default function AppWrapper() { ...prev, defaultCollapseContent: data.ui.default_collapse_content ?? true, expandAll: !!data.ui.default_expand_messages, - hideCot: !!data.ui.hide_cot + hideCot: !!data.ui.hide_cot, + language: data.ui.language as Language })); + handleChangeLanguage(data.ui.language as Language); }, [data, setProjectSettings, setAppSettings]); if (!isReady) { diff --git a/frontend/src/components/atoms/buttons/userButton/menu.tsx b/frontend/src/components/atoms/buttons/userButton/menu.tsx index df1585d7e5..af044cbbf6 100644 --- a/frontend/src/components/atoms/buttons/userButton/menu.tsx +++ b/frontend/src/components/atoms/buttons/userButton/menu.tsx @@ -15,6 +15,8 @@ import { Typography } from '@mui/material'; +import { Translator } from 'components/i18n'; + import { projectSettingsState } from 'state/project'; import { settingsState } from 'state/settings'; @@ -52,9 +54,11 @@ export default function UserMenu({ anchorEl, open, handleClose }: Props) { - Settings + + + - S + ); @@ -64,7 +68,7 @@ export default function UserMenu({ anchorEl, open, handleClose }: Props) { - API keys + ); @@ -79,7 +83,7 @@ export default function UserMenu({ anchorEl, open, handleClose }: Props) { - Logout + ); diff --git a/frontend/src/components/molecules/attachments.tsx b/frontend/src/components/molecules/attachments.tsx index dcdbf94240..3451882003 100644 --- a/frontend/src/components/molecules/attachments.tsx +++ b/frontend/src/components/molecules/attachments.tsx @@ -9,6 +9,7 @@ import Tooltip from '@mui/material/Tooltip'; import { Attachment } from '@chainlit/react-components'; import CircularProgressIconButton from 'components/atoms/buttons/progressIconButton'; +import { Translator } from 'components/i18n'; import { attachmentsState } from 'state/chat'; @@ -31,7 +32,11 @@ const Attachments = (): JSX.Element => { const showProgress = !attachment.uploaded && attachment.cancel; const progress = showProgress ? ( - + + } + > { const remove = !showProgress && attachment.remove ? ( - + + } + > } > - New Chat + void; @@ -31,18 +33,20 @@ export default function NewChatDialog({ {'Create a new chat?'} - This will clear the current messages and start a new chat. + - Cancel + + + - Confirm + diff --git a/frontend/src/components/molecules/settingsModal.tsx b/frontend/src/components/molecules/settingsModal.tsx index 7d4a08aa59..47b2646614 100644 --- a/frontend/src/components/molecules/settingsModal.tsx +++ b/frontend/src/components/molecules/settingsModal.tsx @@ -16,6 +16,8 @@ import { import { SwitchInput } from '@chainlit/react-components'; +import { Translator } from 'components/i18n'; + import { projectSettingsState } from 'state/project'; import { settingsState } from 'state/settings'; @@ -43,7 +45,12 @@ export default function SettingsModal() { - + + } + /> - + + } + /> - + + } + /> { @@ -21,7 +23,7 @@ const Header = ({ status }: { status: string }) => { - 🗒️ Task List + { if (!url) return null; if (!data && !error) { - return
Loading...
; + return ( +
+ +
+ ); } else if (error) { - return
An error occured
; + return ( +
+ +
+ ); } const content = data as ITaskList; diff --git a/frontend/src/components/organisms/chat/Messages/index.tsx b/frontend/src/components/organisms/chat/Messages/index.tsx index 91a66a0873..656d1f1968 100644 --- a/frontend/src/components/organisms/chat/Messages/index.tsx +++ b/frontend/src/components/organisms/chat/Messages/index.tsx @@ -1,5 +1,6 @@ import { apiClient } from 'api'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { toast } from 'sonner'; @@ -39,24 +40,32 @@ const Messages = ({ const accessToken = useRecoilValue(accessTokenState); const setMessages = useSetRecoilState(messagesState); + const { t } = useTranslation(); + const callActionWithToast = useCallback( (action: IAction) => { const promise = callAction(action); if (promise) { toast.promise(promise, { - loading: `Running ${action.name}`, + loading: `${t('components.organisms.chat.Messages.index.running')} ${ + action.name + }`, success: (res) => { if (res.response) { return res.response; } else { - return `${action.name} executed successfully`; + return `${action.name} ${t( + 'components.organisms.chat.Messages.index.executedSuccessfully' + )}`; } }, error: (res) => { if (res.response) { return res.response; } else { - return `${action.name} failed`; + return `${action.name} ${t( + 'components.organisms.chat.Messages.index.failed' + )}`; } } }); @@ -69,7 +78,7 @@ const Messages = ({ async (message: IStep, onSuccess: () => void, feedback: IFeedback) => { try { toast.promise(apiClient.setFeedback(feedback, accessToken), { - loading: 'Updating', + loading: t('components.organisms.chat.Messages.index.updating'), success: (res) => { setMessages((prev) => updateMessageById(prev, message.id, { @@ -81,7 +90,7 @@ const Messages = ({ }) ); onSuccess(); - return 'Feedback updated!'; + return t('components.organisms.chat.Messages.index.Feedback'); }, error: (err) => { return {err.message}; diff --git a/frontend/src/components/organisms/chat/dropScreen.tsx b/frontend/src/components/organisms/chat/dropScreen.tsx index 37e94d251c..c3f2cc4241 100644 --- a/frontend/src/components/organisms/chat/dropScreen.tsx +++ b/frontend/src/components/organisms/chat/dropScreen.tsx @@ -1,5 +1,7 @@ import { Backdrop, Stack, Typography } from '@mui/material'; +import { Translator } from 'components/i18n'; + import FileIcon from 'assets/file'; export default function DropScreen() { @@ -13,7 +15,7 @@ export default function DropScreen() { - Drop your files here + diff --git a/frontend/src/components/organisms/chat/history/index.tsx b/frontend/src/components/organisms/chat/history/index.tsx index e347d81fc5..c6f5ceec54 100644 --- a/frontend/src/components/organisms/chat/history/index.tsx +++ b/frontend/src/components/organisms/chat/history/index.tsx @@ -16,6 +16,8 @@ import { import { UserInput } from '@chainlit/react-client'; import { grey } from '@chainlit/react-components/theme'; +import { Translator } from 'components/i18n'; + import ChevronUpIcon from 'assets/chevronUp'; import { inputHistoryState } from 'state/userInputHistory'; @@ -92,7 +94,7 @@ export default function InputHistoryButton({ disabled, onClick }: Props) { color="text.primary" sx={{ fontSize: '14px', fontWeight: 700 }} > - Last inputs + setInputHistory((old) => ({ ...old, inputs: [] }))} @@ -115,7 +117,7 @@ export default function InputHistoryButton({ disabled, onClick }: Props) { textTransform: 'uppercase' }} > - Such empty... + ) : null; @@ -132,7 +134,7 @@ export default function InputHistoryButton({ disabled, onClick }: Props) { textTransform: 'uppercase' }} > - Loading... + ) : null; diff --git a/frontend/src/components/organisms/chat/index.tsx b/frontend/src/components/organisms/chat/index.tsx index 8c8b6bdfe5..301ae75a4a 100644 --- a/frontend/src/components/organisms/chat/index.tsx +++ b/frontend/src/components/organisms/chat/index.tsx @@ -1,5 +1,6 @@ import { apiClient } from 'api'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { toast } from 'sonner'; import { v4 as uuidv4 } from 'uuid'; @@ -14,6 +15,7 @@ import { import { ErrorBoundary, useUpload } from '@chainlit/react-components'; import SideView from 'components/atoms/element/sideView'; +import { Translator } from 'components/i18n'; import ChatProfiles from 'components/molecules/chatProfiles'; import { TaskList } from 'components/molecules/tasklist/TaskList'; @@ -37,6 +39,8 @@ const Chat = () => { const fileSpec = useMemo(() => ({ max_size_mb: 500 }), []); + const { t } = useTranslation(); + useEffect(() => { uploadFileRef.current = uploadFile; }, [uploadFile]); @@ -83,7 +87,11 @@ const Chat = () => { ); }) .catch((error) => { - toast.error(`Failed to upload ${file.name}: ${error.message}`); + toast.error( + `${t('components.organisms.chat.index.failedToUpload')} ${ + file.name + }: ${error.message}` + ); setAttachments((prev) => prev.filter((attachment) => attachment.id !== id) ); @@ -96,7 +104,11 @@ const Chat = () => { size: file.size, uploadProgress: 0, cancel: () => { - toast.info(`Cancelled upload of ${file.name}`); + toast.info( + `${t('components.organisms.chat.index.cancelledUploadOf')} ${ + file.name + }` + ); xhr.abort(); setAttachments((prev) => prev.filter((attachment) => attachment.id !== id) @@ -167,7 +179,7 @@ const Chat = () => { }} > - Could not reach the server. +
)} diff --git a/frontend/src/components/organisms/chat/inputBox/SubmitButton.tsx b/frontend/src/components/organisms/chat/inputBox/SubmitButton.tsx index 2d3f3b4e44..341fe29cbf 100644 --- a/frontend/src/components/organisms/chat/inputBox/SubmitButton.tsx +++ b/frontend/src/components/organisms/chat/inputBox/SubmitButton.tsx @@ -7,6 +7,8 @@ import InputAdornment from '@mui/material/InputAdornment'; import { useChatData, useChatInteract } from '@chainlit/react-client'; +import { Translator } from 'components/i18n'; + interface SubmitButtonProps { disabled?: boolean; onSubmit: () => void; @@ -28,7 +30,11 @@ const SubmitButton = ({ disabled, onSubmit }: SubmitButtonProps) => { }} > {!loading ? ( - + + } + > @@ -36,7 +42,11 @@ const SubmitButton = ({ disabled, onSubmit }: SubmitButtonProps) => { ) : ( - + + } + > diff --git a/frontend/src/components/organisms/chat/inputBox/UploadButton.tsx b/frontend/src/components/organisms/chat/inputBox/UploadButton.tsx index 851f8d65a3..ecbddcbc5f 100644 --- a/frontend/src/components/organisms/chat/inputBox/UploadButton.tsx +++ b/frontend/src/components/organisms/chat/inputBox/UploadButton.tsx @@ -6,6 +6,8 @@ import { IconButton, Tooltip } from '@mui/material'; import { FileSpec } from '@chainlit/react-client'; import { useUpload } from '@chainlit/react-components'; +import { Translator } from 'components/i18n'; + import { projectSettingsState } from 'state/project'; type Props = { @@ -34,7 +36,11 @@ const UploadButton = ({ const { getRootProps, getInputProps } = upload; return ( - + + } + > { const pasteEvent = (event: ClipboardEvent) => { if (event.clipboardData && event.clipboardData.items) { @@ -203,7 +206,9 @@ const Input = memo( multiline variant="standard" autoComplete="false" - placeholder={'Type your message here...'} + placeholder={t( + 'components.organisms.chat.inputBox.input.placeholder' + )} disabled={disabled} onChange={(e) => setValue(e.target.value)} onKeyDown={handleKeyDown} diff --git a/frontend/src/components/organisms/chat/inputBox/speechButton.tsx b/frontend/src/components/organisms/chat/inputBox/speechButton.tsx index bf504dc87e..a0c4886cc7 100644 --- a/frontend/src/components/organisms/chat/inputBox/speechButton.tsx +++ b/frontend/src/components/organisms/chat/inputBox/speechButton.tsx @@ -7,6 +7,8 @@ import KeyboardVoiceIcon from '@mui/icons-material/KeyboardVoice'; import StopCircleIcon from '@mui/icons-material/StopCircle'; import { IconButton, Tooltip } from '@mui/material'; +import { Translator } from 'components/i18n'; + interface Props { onSpeech: (text: string) => void; language?: string; @@ -46,7 +48,11 @@ const SpeechButton = ({ onSpeech, language, disabled }: Props) => { } return isRecording ? ( - + + } + > { ) : ( - + + } + > - Built with + - {'Settings panel'} + + {} + - Reset +
- Cancel + + + - Confirm + diff --git a/frontend/src/components/organisms/header.tsx b/frontend/src/components/organisms/header.tsx index 4701ddfd09..80d0d33a8a 100644 --- a/frontend/src/components/organisms/header.tsx +++ b/frontend/src/components/organisms/header.tsx @@ -1,5 +1,6 @@ import { useAuth } from 'api/auth'; import { memo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Link, useLocation } from 'react-router-dom'; import MenuIcon from '@mui/icons-material/Menu'; @@ -69,16 +70,21 @@ const Nav = ({ dataPersistence, hasReadme, matches }: NavProps) => { const [open, setOpen] = useState(false); const ref = useRef(); + const { t } = useTranslation(); + let anchorEl; if (open && ref.current) { anchorEl = ref.current; } - const tabs = [{ to: '/', label: 'Chat' }]; + const tabs = [{ to: '/', label: t('components.organisms.header.chat') }]; if (hasReadme) { - tabs.push({ to: '/readme', label: 'Readme' }); + tabs.push({ + to: '/readme', + label: t('components.organisms.header.readme') + }); } const nav = ( diff --git a/frontend/src/components/organisms/threadHistory/Thread.tsx b/frontend/src/components/organisms/threadHistory/Thread.tsx index 144abda786..9d860c0537 100644 --- a/frontend/src/components/organisms/threadHistory/Thread.tsx +++ b/frontend/src/components/organisms/threadHistory/Thread.tsx @@ -17,6 +17,7 @@ import { } from '@chainlit/react-client'; import SideView from 'components/atoms/element/sideView'; +import { Translator } from 'components/i18n'; import MessageContainer from 'components/organisms/chat/Messages/container'; type Props = { @@ -122,11 +123,11 @@ const Thread = ({ thread, error, isLoading }: Props) => { severity="info" action={ } > - This chat was created on{' '} + {' '} {new Intl.DateTimeFormat(undefined, { day: 'numeric', month: 'numeric', diff --git a/frontend/src/components/organisms/threadHistory/sidebar/DeleteThreadButton.tsx b/frontend/src/components/organisms/threadHistory/sidebar/DeleteThreadButton.tsx index 8c4ef0537f..b1ed4006f5 100644 --- a/frontend/src/components/organisms/threadHistory/sidebar/DeleteThreadButton.tsx +++ b/frontend/src/components/organisms/threadHistory/sidebar/DeleteThreadButton.tsx @@ -15,6 +15,8 @@ import DialogTitle from '@mui/material/DialogTitle'; import { ClientError, accessTokenState } from '@chainlit/react-client'; +import { Translator } from 'components/i18n'; + interface Props { threadId: string; onDelete: () => void; @@ -34,10 +36,14 @@ const DeleteThreadButton = ({ threadId, onDelete }: Props) => { const handleConfirm = async () => { toast.promise(apiClient.deleteThread(threadId, accessToken), { - loading: 'Deleting chat', + loading: ( + + ), success: () => { onDelete(); - return 'Chat deleted!'; + return ( + + ); }, error: (err) => { if (err instanceof ClientError) { @@ -70,12 +76,12 @@ const DeleteThreadButton = ({ threadId, onDelete }: Props) => { {'Delete Thread?'} - This will delete the thread as well as it's messages and elements. + { onClick={handleConfirm} autoFocus > - Confirm + diff --git a/frontend/src/components/organisms/threadHistory/sidebar/ThreadList.tsx b/frontend/src/components/organisms/threadHistory/sidebar/ThreadList.tsx index 5ac81713e5..59c15c8622 100644 --- a/frontend/src/components/organisms/threadHistory/sidebar/ThreadList.tsx +++ b/frontend/src/components/organisms/threadHistory/sidebar/ThreadList.tsx @@ -20,6 +20,8 @@ import { } from '@chainlit/react-client'; import { grey } from '@chainlit/react-components'; +import { Translator } from 'components/i18n'; + import { DeleteThreadButton } from './DeleteThreadButton'; interface Props { @@ -77,7 +79,7 @@ const ThreadList = ({ if (size(threadHistory?.timeGroupedThreads) === 0) { return ( - Empty... + ); } diff --git a/frontend/src/components/organisms/threadHistory/sidebar/TriggerButton.tsx b/frontend/src/components/organisms/threadHistory/sidebar/TriggerButton.tsx index 1b09fc7d45..5ad53674b4 100644 --- a/frontend/src/components/organisms/threadHistory/sidebar/TriggerButton.tsx +++ b/frontend/src/components/organisms/threadHistory/sidebar/TriggerButton.tsx @@ -1,3 +1,5 @@ +import { useTranslation } from 'react-i18next'; + import Box from '@mui/material/Box'; import Tooltip from '@mui/material/Tooltip'; @@ -14,8 +16,21 @@ const commonBoxStyles = { }; const TriggerButton = ({ onClick, open }: TriggerButtonProps): JSX.Element => { + const { t } = useTranslation(); + return ( - + (null); + const { t } = useTranslation(); + const handleChange = (feedback: number) => { setFilters((prev) => ({ ...prev, feedback })); setAnchorEl(null); @@ -96,9 +99,24 @@ export default function FeedbackSelect() { }} > - {renderMenuItem('Feedback: All', FEEDBACKS.ALL)} - {renderMenuItem('Feedback: Positive', FEEDBACKS.POSITIVE)} - {renderMenuItem('Feedback: Negative', FEEDBACKS.NEGATIVE)} + {renderMenuItem( + t( + 'components.organisms.threadHistory.sidebar.filters.FeedbackSelect.feedbackAll' + ), + FEEDBACKS.ALL + )} + {renderMenuItem( + t( + 'components.organisms.threadHistory.sidebar.filters.FeedbackSelect.feedbackPositive' + ), + FEEDBACKS.POSITIVE + )} + {renderMenuItem( + t( + 'components.organisms.threadHistory.sidebar.filters.FeedbackSelect.feedbackNegative' + ), + FEEDBACKS.NEGATIVE + )} diff --git a/frontend/src/components/organisms/threadHistory/sidebar/filters/SearchBar.tsx b/frontend/src/components/organisms/threadHistory/sidebar/filters/SearchBar.tsx index 10137e9751..0e1683b16a 100644 --- a/frontend/src/components/organisms/threadHistory/sidebar/filters/SearchBar.tsx +++ b/frontend/src/components/organisms/threadHistory/sidebar/filters/SearchBar.tsx @@ -1,5 +1,6 @@ import debounce from 'lodash/debounce'; import { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRecoilState } from 'recoil'; import CloseIcon from '@mui/icons-material/Close'; @@ -15,6 +16,8 @@ import { threadsFiltersState } from 'state/threads'; export default function SearchBar() { const [filters, setFilters] = useRecoilState(threadsFiltersState); + const { t } = useTranslation(); + const handleChange = (value: string) => { value = value.trim(); const search = value === '' ? undefined : value; @@ -61,7 +64,9 @@ export default function SearchBar() { ) : null }} - placeholder="Search" + placeholder={t( + 'components.organisms.threadHistory.sidebar.filters.SearchBar.search' + )} inputProps={{ 'aria-label': 'search', ref: inputRef, diff --git a/frontend/src/components/organisms/threadHistory/sidebar/index.tsx b/frontend/src/components/organisms/threadHistory/sidebar/index.tsx index 76476a624f..b0a1e79012 100644 --- a/frontend/src/components/organisms/threadHistory/sidebar/index.tsx +++ b/frontend/src/components/organisms/threadHistory/sidebar/index.tsx @@ -17,6 +17,8 @@ import { threadHistoryState } from '@chainlit/react-client'; +import { Translator } from 'components/i18n'; + import { projectSettingsState } from 'state/project'; import { settingsState } from 'state/settings'; import { threadsFiltersState } from 'state/threads'; @@ -172,7 +174,7 @@ const _ThreadHistorySideBar = () => { color: (theme) => theme.palette.text.primary }} > - Past Chats + diff --git a/frontend/src/hooks/useLLMProviders.ts b/frontend/src/hooks/useLLMProviders.ts index d38c370c35..ab2509acb3 100644 --- a/frontend/src/hooks/useLLMProviders.ts +++ b/frontend/src/hooks/useLLMProviders.ts @@ -1,5 +1,6 @@ import { apiClient } from 'api'; import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { useSetRecoilState } from 'recoil'; import { toast } from 'sonner'; @@ -15,9 +16,13 @@ const useLLMProviders = (shouldFetch?: boolean) => { ); const setPlayground = useSetRecoilState(playgroundState); + const { t } = useTranslation(); + useEffect(() => { if (error) { - toast.error(`Failed to fetch providers: ${error}`); + toast.error( + `${t('hooks.useLLMProviders.failedToFetchProviders')} ${error}` + ); } if (!data) return; setPlayground((old) => ({ ...old, providers: data.providers })); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f20557cf2b..18e1ec85fd 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,8 @@ import { RecoilRoot } from 'recoil'; import './index.css'; +import './i18n'; + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/frontend/src/pages/Env.tsx b/frontend/src/pages/Env.tsx index fb57f520f9..dc7509a9d1 100644 --- a/frontend/src/pages/Env.tsx +++ b/frontend/src/pages/Env.tsx @@ -1,4 +1,5 @@ import { useFormik } from 'formik'; +import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useRecoilState, useRecoilValue } from 'recoil'; import { toast } from 'sonner'; @@ -8,6 +9,7 @@ import { Alert, Box, Button, Typography } from '@mui/material'; import { TextInput } from '@chainlit/react-components'; +import { Translator } from 'components/i18n'; import { Header } from 'components/organisms/header'; import { projectSettingsState } from 'state/project'; @@ -19,6 +21,8 @@ export default function Env() { const navigate = useNavigate(); + const { t } = useTranslation(); + const requiredKeys = pSettings?.userEnv || []; const initialValues: Record = {}; @@ -37,7 +41,7 @@ export default function Env() { onSubmit: async (values) => { localStorage.setItem('userEnv', JSON.stringify(values)); setUserEnv(values); - toast.success('Saved successfully'); + toast.success(t('pages.Env.savedSuccessfully')); return navigate('/'); } }); @@ -92,11 +96,10 @@ export default function Env() { fontWeight={700} color="text.primary" > - Required API keys + - To use this app, the following API keys are required. The keys are - stored on your device's local storage. +
{requiredKeys.map((key) => renderInput(key))} diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 8f90e746c9..800438a451 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,6 +1,7 @@ import { apiClient } from 'api'; import { useAuth } from 'api/auth'; import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { AuthLogin } from '@chainlit/react-components'; @@ -16,6 +17,8 @@ export default function Login() { const navigate = useNavigate(); + const { t } = useTranslation(); + const handleHeaderAuth = async () => { try { const json = await apiClient.headerAuth(); @@ -65,7 +68,7 @@ export default function Login() { return ( { >
{!isAuthenticated ? ( - You are not part of this project. + + + ) : ( diff --git a/frontend/src/pages/ResumeButton.tsx b/frontend/src/pages/ResumeButton.tsx index 93426dcc29..c4848b37b8 100644 --- a/frontend/src/pages/ResumeButton.tsx +++ b/frontend/src/pages/ResumeButton.tsx @@ -6,6 +6,7 @@ import { Box, Button } from '@mui/material'; import { useChatInteract } from '@chainlit/react-client'; +import { Translator } from 'components/i18n'; import WaterMark from 'components/organisms/chat/inputBox/waterMark'; import { projectSettingsState } from 'state/project'; @@ -45,7 +46,7 @@ export default function ResumeButton({ threadId }: Props) { }} > diff --git a/frontend/src/state/project.ts b/frontend/src/state/project.ts index a98d8c89d6..ba72f44c76 100644 --- a/frontend/src/state/project.ts +++ b/frontend/src/state/project.ts @@ -18,6 +18,7 @@ export interface IProjectSettings { default_collapse_content?: boolean; default_expand_messages?: boolean; github?: string; + language?: string; }; features: { multi_modal?: boolean; diff --git a/frontend/src/state/settings.ts b/frontend/src/state/settings.ts index ece6936086..b52fa2a753 100644 --- a/frontend/src/state/settings.ts +++ b/frontend/src/state/settings.ts @@ -2,6 +2,8 @@ import { atom } from 'recoil'; type ThemeVariant = 'dark' | 'light'; +export type Language = 'en-US' | 'pt-BR'; + const defaultTheme = 'light'; const preferredTheme = localStorage.getItem( @@ -16,6 +18,7 @@ export const defaultSettingsState = { expandAll: false, hideCot: false, isChatHistoryOpen: true, + language: 'en-US' as Language, theme }; @@ -26,6 +29,7 @@ export const settingsState = atom<{ hideCot: boolean; theme: ThemeVariant; isChatHistoryOpen: boolean; + language: Language; }>({ key: 'AppSettings', default: defaultSettingsState From 53638909878e0572406b32fcd2b4efd746e36f84 Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Sat, 13 Jan 2024 14:11:44 -0300 Subject: [PATCH 03/14] feature: updating chainlit config files in tests --- cypress/e2e/ask_multiple_files/.chainlit/config.toml | 3 +++ cypress/e2e/chat_profiles/.chainlit/config.toml | 3 +++ cypress/e2e/chat_settings/.chainlit/config.toml | 3 +++ cypress/e2e/plotly/.chainlit/config.toml | 3 +++ cypress/e2e/upload_attachments/.chainlit/config.toml | 3 +++ 5 files changed, 15 insertions(+) diff --git a/cypress/e2e/ask_multiple_files/.chainlit/config.toml b/cypress/e2e/ask_multiple_files/.chainlit/config.toml index 92e7b496fe..bc9974deb7 100644 --- a/cypress/e2e/ask_multiple_files/.chainlit/config.toml +++ b/cypress/e2e/ask_multiple_files/.chainlit/config.toml @@ -47,6 +47,9 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" +# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". +# language = "en-US" + # Allows user to use speech to text # speech_to_text = true diff --git a/cypress/e2e/chat_profiles/.chainlit/config.toml b/cypress/e2e/chat_profiles/.chainlit/config.toml index f6ae6374f1..8ef013ff25 100644 --- a/cypress/e2e/chat_profiles/.chainlit/config.toml +++ b/cypress/e2e/chat_profiles/.chainlit/config.toml @@ -59,6 +59,9 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" +# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". +# language = "en-US" + # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" diff --git a/cypress/e2e/chat_settings/.chainlit/config.toml b/cypress/e2e/chat_settings/.chainlit/config.toml index 8c46822f75..632177f71b 100644 --- a/cypress/e2e/chat_settings/.chainlit/config.toml +++ b/cypress/e2e/chat_settings/.chainlit/config.toml @@ -41,6 +41,9 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" +# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". +# language = "en-US" + # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" diff --git a/cypress/e2e/plotly/.chainlit/config.toml b/cypress/e2e/plotly/.chainlit/config.toml index b59cf19f81..51728325a5 100644 --- a/cypress/e2e/plotly/.chainlit/config.toml +++ b/cypress/e2e/plotly/.chainlit/config.toml @@ -53,6 +53,9 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" +# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". +# language = "en-US" + # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" diff --git a/cypress/e2e/upload_attachments/.chainlit/config.toml b/cypress/e2e/upload_attachments/.chainlit/config.toml index dd4a16cec1..5ef98c7b73 100644 --- a/cypress/e2e/upload_attachments/.chainlit/config.toml +++ b/cypress/e2e/upload_attachments/.chainlit/config.toml @@ -41,6 +41,9 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" +# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". +# language = "en-US" + # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" From ae49f57fa4228c5df82406c7f959e5f3702e1cb2 Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Sat, 13 Jan 2024 14:31:52 -0300 Subject: [PATCH 04/14] feature: adding language variable to config file --- backend/chainlit/config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index 4ea11a626f..97db3ccfaa 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -94,6 +94,9 @@ # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" +# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". +# language = "en-US" + # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" @@ -185,6 +188,8 @@ class UISettings(DataClassJsonMixin): theme: Optional[Theme] = None # Optional custom CSS file that allows you to customize the UI custom_css: Optional[str] = None + # Select language for the UI. https://support.mozilla.org/pt-BR/kb/abreviacao-de-localizacao + language: Optional[str] = None @dataclass() From e48ea7ba3d2307e003ce759c67977341f2223e08 Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Mon, 15 Jan 2024 08:57:15 -0300 Subject: [PATCH 05/14] fix: taskList update path --- frontend/src/components/molecules/tasklist/TaskList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/molecules/tasklist/TaskList.tsx b/frontend/src/components/molecules/tasklist/TaskList.tsx index ad5d0905fc..e0b4d55ab3 100644 --- a/frontend/src/components/molecules/tasklist/TaskList.tsx +++ b/frontend/src/components/molecules/tasklist/TaskList.tsx @@ -23,7 +23,7 @@ const Header = ({ status }: { status: string }) => { - + { if (!data && !error) { return (
- +
); } else if (error) { return (
- +
); } From 867cb0f106131731f8ce41194d021e0f4d3f5fac Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Mon, 15 Jan 2024 08:58:55 -0300 Subject: [PATCH 06/14] fix: changed tag waterMark "buildWith" -> "text" --- frontend/src/components/organisms/chat/inputBox/waterMark.tsx | 2 +- frontend/src/i18n/locales/en-us.ts | 2 +- frontend/src/i18n/locales/pt-br.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/organisms/chat/inputBox/waterMark.tsx b/frontend/src/components/organisms/chat/inputBox/waterMark.tsx index cc9e85a1d9..8e5f7a56f8 100644 --- a/frontend/src/components/organisms/chat/inputBox/waterMark.tsx +++ b/frontend/src/components/organisms/chat/inputBox/waterMark.tsx @@ -24,7 +24,7 @@ export default function WaterMark() { }} > - + Date: Mon, 15 Jan 2024 12:41:03 -0300 Subject: [PATCH 07/14] fix: removing locale files and creating i18n start function --- frontend/src/i18n/index.ts | 14 +-- frontend/src/i18n/locales/en-us.ts | 161 ----------------------------- frontend/src/i18n/locales/index.ts | 7 -- frontend/src/i18n/locales/pt-br.ts | 161 ----------------------------- 4 files changed, 7 insertions(+), 336 deletions(-) delete mode 100644 frontend/src/i18n/locales/en-us.ts delete mode 100644 frontend/src/i18n/locales/index.ts delete mode 100644 frontend/src/i18n/locales/pt-br.ts diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index dc62d2623c..9fbeaba629 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,14 +1,14 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -import translations from './locales'; - const i18nConfig = { - resources: translations, fallbackLng: 'en-US', - defaultNS: 'translations' + defaultNS: 'translation' }; -i18n.use(initReactI18next).init(i18nConfig); - -export default i18n; +export function i18nSetupLocalization(): void { + i18n + .use(initReactI18next) + .init(i18nConfig) + .catch((err) => console.error('[i18n] Failed to setup localization.', err)); +} diff --git a/frontend/src/i18n/locales/en-us.ts b/frontend/src/i18n/locales/en-us.ts deleted file mode 100644 index 06672f1dc5..0000000000 --- a/frontend/src/i18n/locales/en-us.ts +++ /dev/null @@ -1,161 +0,0 @@ -export default { - translations: { - components: { - atoms: { - buttons: { - userButton: { - menu: { - settings: 'Settings', - settingsKey: 'S', - APIKeys: 'API Keys', - logout: 'Logout' - } - } - } - }, - molecules: { - newChatButton: { - newChat: 'New Chat' - }, - tasklist: { - TaskList: { - title: '🗒️ Task List', - loading: 'Loading...', - error: 'An error occured' - } - }, - attachments: { - cancelUpload: 'Cancel upload', - removeAttachment: 'Remove attachment' - }, - newChatDialog: { - clearChat: - 'This will clear the current messages and start a new chat.', - cancel: 'Cancel', - confirm: 'Confirm' - }, - settingsModal: { - expandMessages: 'Expand Messages', - hideChainOfThought: 'Hide Chain of Thought', - darkMode: 'Dark Mode' - } - }, - organisms: { - chat: { - history: { - index: { - lastInputs: 'Last Inputs', - noInputs: 'Such empty...', - loading: 'Loading...' - } - }, - inputBox: { - input: { - placeholder: 'Type your message here...' - }, - speechButton: { - start: 'Start recording', - stop: 'Stop recording' - }, - SubmitButton: { - sendMessage: 'Send message', - stopTask: 'Stop Task' - }, - UploadButton: { - attachFiles: 'Attach files' - }, - waterMark: { - text: 'Build with' - } - }, - Messages: { - index: { - running: 'Running', - executedSuccessfully: 'executed successfully', - failed: 'failed', - feedbackUpdated: 'Feedback updated', - updating: 'Updating' - } - }, - dropScreen: { - dropYourFilesHere: 'Drop your files here' - }, - index: { - failedToUpload: 'Failed to upload', - cancelledUploadOf: 'Cancelled upload of', - couldNotReachServer: 'Could not reach the server' - }, - settings: { - settingsPanel: 'Settings panel', - reset: 'Reset', - cancel: 'Cancel', - confirm: 'Confirm' - } - }, - threadHistory: { - sidebar: { - filters: { - FeedbackSelect: { - feedbackAll: 'Feedback: All', - feedbackPositive: 'Feedback: Positive', - feedbackNegative: 'Feedback: Negative' - }, - SearchBar: { - search: 'Search' - } - }, - DeleteThreadButton: { - confirmMessage: - "This will delete the thread as well as it's messages and elements.", - cancel: 'Cancel', - confirm: 'Confirm', - deletingChat: 'Deleting chat', - chatDeleted: 'Chat deleted' - }, - index: { - pastChats: 'Past Chats' - }, - ThreadList: { - empty: 'Empty...' - }, - TriggerButton: { - closeSidebar: 'Close sidebar', - openSidebar: 'Open sidebar' - } - }, - Thread: { - backToChat: 'Go back to chat', - chatCreatedOn: 'This chat was created on' - } - }, - header: { - chat: 'Chat', - readme: 'Readme' - } - } - }, - hooks: { - useLLMProviders: { - failedToFetchProviders: 'Failed to fetch providers:' - } - }, - pages: { - Design: {}, - Env: { - savedSuccessfully: 'Saved successfully', - requiredApiKeys: 'Required API Keys', - requiredApiKeysInfo: - "To use this app, the following API keys are required. The keys are stored on your device's local storage." - }, - Login: { - authTitle: 'Login to access the app.' - }, - Page: { - notPartOfProject: 'You are not part of this project.' - }, - ResumeButton: { - resumeChat: 'Resume Chat' - } - } - } -}; diff --git a/frontend/src/i18n/locales/index.ts b/frontend/src/i18n/locales/index.ts deleted file mode 100644 index f920071b75..0000000000 --- a/frontend/src/i18n/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import enUsTranslations from './en-us'; -import ptBrTranslations from './pt-br'; - -export default { - 'en-US': enUsTranslations, - 'pt-BR': ptBrTranslations -}; diff --git a/frontend/src/i18n/locales/pt-br.ts b/frontend/src/i18n/locales/pt-br.ts deleted file mode 100644 index 39d6cf3ed8..0000000000 --- a/frontend/src/i18n/locales/pt-br.ts +++ /dev/null @@ -1,161 +0,0 @@ -export default { - translations: { - components: { - atoms: { - buttons: { - userButton: { - menu: { - settings: 'Configurações', - settingsKey: 'S', - APIKeys: 'Chaves de API', - logout: 'Sair' - } - } - } - }, - molecules: { - newChatButton: { - newChat: 'Nova Conversa' - }, - tasklist: { - TaskList: { - title: '🗒️ Lista de Tarefas', - loading: 'Carregando...', - error: 'Ocorreu um erro' - } - }, - attachments: { - cancelUpload: 'Cancelar upload', - removeAttachment: 'Remover anexo' - }, - newChatDialog: { - clearChat: - 'Isso limpará as mensagens atuais e iniciará uma nova conversa.', - cancel: 'Cancelar', - confirm: 'Confirmar' - }, - settingsModal: { - expandMessages: 'Expandir Mensagens', - hideChainOfThought: 'Esconder Modo de Pensamento', - darkMode: 'Modo Escuro' - } - }, - organisms: { - chat: { - history: { - index: { - lastInputs: 'Últimas Entradas', - noInputs: 'Vazio...', - loading: 'Carregando...' - } - }, - inputBox: { - input: { - placeholder: 'Digite sua mensagem aqui...' - }, - speechButton: { - start: 'Iniciar gravação', - stop: 'Parar gravação' - }, - SubmitButton: { - sendMessage: 'Enviar mensagem', - stopTask: 'Parar Tarefa' - }, - UploadButton: { - attachFiles: 'Anexar arquivos' - }, - waterMark: { - text: 'Construído com' - } - }, - Messages: { - index: { - running: 'Executando', - executedSuccessfully: 'executado com sucesso', - failed: 'falhou', - feedbackUpdated: 'Feedback atualizado', - updating: 'Atualizando' - } - }, - dropScreen: { - dropYourFilesHere: 'Arraste seus arquivos aqui' - }, - index: { - failedToUpload: 'Falha ao enviar', - cancelledUploadOf: 'Envio cancelado de', - couldNotReachServer: 'Não foi possível conectar ao servidor' - }, - settings: { - settingsPanel: 'Painel de Configurações', - reset: 'Redefinir', - cancel: 'Cancelar', - confirm: 'Confirmar' - } - }, - threadHistory: { - sidebar: { - filters: { - FeedbackSelect: { - feedbackAll: 'Feedback: Todos', - feedbackPositive: 'Feedback: Positivo', - feedbackNegative: 'Feedback: Negativo' - }, - SearchBar: { - search: 'Pesquisar' - } - }, - DeleteThreadButton: { - confirmMessage: - 'Isso deletará o tópico, bem como suas mensagens e elementos.', - cancel: 'Cancelar', - confirm: 'Confirmar', - deletingChat: 'Excluindo conversa', - chatDeleted: 'Conversa excluída' - }, - index: { - pastChats: 'Conversas Anteriores' - }, - ThreadList: { - empty: 'Vazio...' - }, - TriggerButton: { - closeSidebar: 'Fechar barra lateral', - openSidebar: 'Abrir barra lateral' - } - }, - Thread: { - backToChat: 'Voltar para a conversa', - chatCreatedOn: 'Esta conversa foi criada em' - } - }, - header: { - chat: 'Conversa', - readme: 'Sobre' - } - } - }, - hooks: { - useLLMProviders: { - failedToFetchProviders: 'Falha ao buscar provedores:' - } - }, - pages: { - Design: {}, - Env: { - savedSuccessfully: 'Salvo com sucesso', - requiredApiKeys: 'Chaves de API necessárias', - requiredApiKeysInfo: - 'Para usar este aplicativo, as seguintes chaves de API são necessárias. As chaves são armazenadas no armazenamento local do seu dispositivo.' - }, - Login: { - authTitle: 'Faça login para acessar o aplicativo.' - }, - Page: { - notPartOfProject: 'Você não faz parte deste projeto.' - }, - ResumeButton: { - resumeChat: 'Continuar Conversa' - } - } - } -}; From 2240abb629654f4f853b1ffe334d236dbdcbbf4d Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Mon, 15 Jan 2024 12:42:29 -0300 Subject: [PATCH 08/14] feature: language translations in init server config router --- backend/chainlit/config.py | 26 +++- backend/chainlit/server.py | 17 ++- backend/chainlit/translations/en-US.json | 158 +++++++++++++++++++++++ backend/chainlit/translations/pt-BR.json | 157 ++++++++++++++++++++++ 4 files changed, 350 insertions(+), 8 deletions(-) create mode 100644 backend/chainlit/translations/en-US.json create mode 100644 backend/chainlit/translations/pt-BR.json diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index 97db3ccfaa..22ef102302 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -1,3 +1,4 @@ +import json import os import sys from importlib import util @@ -20,6 +21,7 @@ BACKEND_ROOT = os.path.dirname(__file__) PACKAGE_ROOT = os.path.dirname(os.path.dirname(BACKEND_ROOT)) +TRANSLATIONS_FOLDER = os.path.join(BACKEND_ROOT, "translations") # Get the directory the script is running from @@ -94,9 +96,6 @@ # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" -# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". -# language = "en-US" - # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" @@ -188,8 +187,6 @@ class UISettings(DataClassJsonMixin): theme: Optional[Theme] = None # Optional custom CSS file that allows you to customize the UI custom_css: Optional[str] = None - # Select language for the UI. https://support.mozilla.org/pt-BR/kb/abreviacao-de-localizacao - language: Optional[str] = None @dataclass() @@ -245,6 +242,25 @@ class ChainlitConfig: project: ProjectSettings code: CodeSettings + def load_translation(self, language: str): + translation = {} + + translation_lib_file_path = os.path.join( + TRANSLATIONS_FOLDER, f"{language}.json" + ) + default_translation_lib_file_path = os.path.join( + TRANSLATIONS_FOLDER, f"en-US.json" + ) + + if os.path.exists(translation_lib_file_path): + with open(translation_lib_file_path, "r", encoding="utf-8") as f: + translation = json.load(f) + elif os.path.exists(default_translation_lib_file_path): + with open(default_translation_lib_file_path, "r", encoding="utf-8") as f: + translation = json.load(f) + + return translation + def init_config(log=False): """Initialize the configuration file if it doesn't exist.""" diff --git a/backend/chainlit/server.py b/backend/chainlit/server.py index bee50f8fb8..fdf47ad4e0 100644 --- a/backend/chainlit/server.py +++ b/backend/chainlit/server.py @@ -342,9 +342,14 @@ async def oauth_login(provider_id: str, request: Request): url=f"{provider.authorize_url}?{params}", ) samesite = os.environ.get("CHAINLIT_COOKIE_SAMESITE", "lax") # type: Any - secure = samesite.lower() == 'none' + secure = samesite.lower() == "none" response.set_cookie( - "oauth_state", random, httponly=True, samesite=samesite, secure=secure, max_age=3 * 60 + "oauth_state", + random, + httponly=True, + samesite=samesite, + secure=secure, + max_age=3 * 60, ) return response @@ -469,9 +474,14 @@ async def get_providers( @app.get("/project/settings") async def project_settings( - current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)] + current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)], + language: str = Query(default="en-US", description="Language code"), ): """Return project settings. This is called by the UI before the establishing the websocket connection.""" + + # Load translation based on the provided language + translation = config.load_translation(language) + profiles = [] if config.code.set_chat_profiles: chat_profiles = await config.code.set_chat_profiles(current_user) @@ -486,6 +496,7 @@ async def project_settings( "threadResumable": bool(config.code.on_chat_resume), "markdown": get_markdown_str(config.root), "chatProfiles": profiles, + "translation": translation, } ) diff --git a/backend/chainlit/translations/en-US.json b/backend/chainlit/translations/en-US.json new file mode 100644 index 0000000000..5bc66ee6dd --- /dev/null +++ b/backend/chainlit/translations/en-US.json @@ -0,0 +1,158 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "Settings", + "settingsKey": "S", + "APIKeys": "API Keys", + "logout": "Logout" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "New Chat" + }, + "tasklist": { + "TaskList": { + "title": "🗒️ Task List", + "loading": "Loading...", + "error": "An error occured" + } + }, + "attachments": { + "cancelUpload": "Cancel upload", + "removeAttachment": "Remove attachment" + }, + "newChatDialog": { + "createNewChat": "Create new chat?", + "clearChat": "This will clear the current messages and start a new chat.", + "cancel": "Cancel", + "confirm": "Confirm" + }, + "settingsModal": { + "expandMessages": "Expand Messages", + "hideChainOfThought": "Hide Chain of Thought", + "darkMode": "Dark Mode" + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "lastInputs": "Last Inputs", + "noInputs": "Such empty...", + "loading": "Loading..." + } + }, + "inputBox": { + "input": { + "placeholder": "Type your message here..." + }, + "speechButton": { + "start": "Start recording", + "stop": "Stop recording" + }, + "SubmitButton": { + "sendMessage": "Send message", + "stopTask": "Stop Task" + }, + "UploadButton": { + "attachFiles": "Attach files" + }, + "waterMark": { + "text": "Build with" + } + }, + "Messages": { + "index": { + "running": "Running", + "executedSuccessfully": "executed successfully", + "failed": "failed", + "feedbackUpdated": "Feedback updated", + "updating": "Updating" + } + }, + "dropScreen": { + "dropYourFilesHere": "Drop your files here" + }, + "index": { + "failedToUpload": "Failed to upload", + "cancelledUploadOf": "Cancelled upload of", + "couldNotReachServer": "Could not reach the server" + }, + "settings": { + "settingsPanel": "Settings panel", + "reset": "Reset", + "cancel": "Cancel", + "confirm": "Confirm" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "Feedback: All", + "feedbackPositive": "Feedback: Positive", + "feedbackNegative": "Feedback: Negative" + }, + "SearchBar": { + "search": "Search" + } + }, + "DeleteThreadButton": { + "confirmMessage": "This will delete the thread as well as it's messages and elements.", + "cancel": "Cancel", + "confirm": "Confirm", + "deletingChat": "Deleting chat", + "chatDeleted": "Chat deleted" + }, + "index": { + "pastChats": "Past Chats" + }, + "ThreadList": { + "empty": "Empty..." + }, + "TriggerButton": { + "closeSidebar": "Close sidebar", + "openSidebar": "Open sidebar" + } + }, + "Thread": { + "backToChat": "Go back to chat", + "chatCreatedOn": "This chat was created on" + } + }, + "header": { + "chat": "Chat", + "readme": "Readme" + } + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "Failed to fetch providers:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "Saved successfully", + "requiredApiKeys": "Required API Keys", + "requiredApiKeysInfo": "To use this app, the following API keys are required. The keys are stored on your device's local storage." + }, + "Login": { + "authTitle": "Login to access the app." + }, + "Page": { + "notPartOfProject": "You are not part of this project." + }, + "ResumeButton": { + "resumeChat": "Resume Chat" + } + } + } + \ No newline at end of file diff --git a/backend/chainlit/translations/pt-BR.json b/backend/chainlit/translations/pt-BR.json new file mode 100644 index 0000000000..cd45c9ce8e --- /dev/null +++ b/backend/chainlit/translations/pt-BR.json @@ -0,0 +1,157 @@ +{ + "components": { + "atoms": { + "buttons": { + "userButton": { + "menu": { + "settings": "Configurações", + "settingsKey": "S", + "APIKeys": "Chaves de API", + "logout": "Sair" + } + } + } + }, + "molecules": { + "newChatButton": { + "newChat": "Nova Conversa" + }, + "tasklist": { + "TaskList": { + "title": "🗒️ Lista de Tarefas", + "loading": "Carregando...", + "error": "Ocorreu um erro" + } + }, + "attachments": { + "cancelUpload": "Cancelar envio", + "removeAttachment": "Remover anexo" + }, + "newChatDialog": { + "createNewChat": "Criar novo chat?", + "clearChat": "Isso limpará as mensagens atuais e iniciará uma nova conversa.", + "cancel": "Cancelar", + "confirm": "Confirmar" + }, + "settingsModal": { + "expandMessages": "Expandir Mensagens", + "hideChainOfThought": "Esconder Sequência de Pensamento", + "darkMode": "Modo Escuro" + } + }, + "organisms": { + "chat": { + "history": { + "index": { + "lastInputs": "Últimas Entradas", + "noInputs": "Vazio...", + "loading": "Carregando..." + } + }, + "inputBox": { + "input": { + "placeholder": "Digite sua mensagem aqui..." + }, + "speechButton": { + "start": "Iniciar gravação", + "stop": "Parar gravação" + }, + "SubmitButton": { + "sendMessage": "Enviar mensagem", + "stopTask": "Parar Tarefa" + }, + "UploadButton": { + "attachFiles": "Anexar arquivos" + }, + "waterMark": { + "text": "Construído com" + } + }, + "Messages": { + "index": { + "running": "Executando", + "executedSuccessfully": "executado com sucesso", + "failed": "falhou", + "feedbackUpdated": "Feedback atualizado", + "updating": "Atualizando" + } + }, + "dropScreen": { + "dropYourFilesHere": "Solte seus arquivos aqui" + }, + "index": { + "failedToUpload": "Falha ao enviar", + "cancelledUploadOf": "Envio cancelado de", + "couldNotReachServer": "Não foi possível conectar ao servidor" + }, + "settings": { + "settingsPanel": "Painel de Configurações", + "reset": "Redefinir", + "cancel": "Cancelar", + "confirm": "Confirmar" + } + }, + "threadHistory": { + "sidebar": { + "filters": { + "FeedbackSelect": { + "feedbackAll": "Feedback: Todos", + "feedbackPositive": "Feedback: Positivo", + "feedbackNegative": "Feedback: Negativo" + }, + "SearchBar": { + "search": "Buscar" + } + }, + "DeleteThreadButton": { + "confirmMessage": "Isso deletará a conversa, assim como suas mensagens e elementos.", + "cancel": "Cancelar", + "confirm": "Confirmar", + "deletingChat": "Deletando conversa", + "chatDeleted": "Conversa deletada" + }, + "index": { + "pastChats": "Conversas Anteriores" + }, + "ThreadList": { + "empty": "Vazio..." + }, + "TriggerButton": { + "closeSidebar": "Fechar barra lateral", + "openSidebar": "Abrir barra lateral" + } + }, + "Thread": { + "backToChat": "Voltar para a conversa", + "chatCreatedOn": "Esta conversa foi criada em" + } + }, + "header": { + "chat": "Conversa", + "readme": "Leia-me" + } + }, + "hooks": { + "useLLMProviders": { + "failedToFetchProviders": "Falha ao buscar provedores:" + } + }, + "pages": { + "Design": {}, + "Env": { + "savedSuccessfully": "Salvo com sucesso", + "requiredApiKeys": "Chaves de API necessárias", + "requiredApiKeysInfo": "Para usar este aplicativo, as seguintes chaves de API são necessárias. As chaves são armazenadas localmente em seu dispositivo." + }, + "Login": { + "authTitle": "Faça login para acessar o aplicativo." + }, + "Page": { + "notPartOfProject": "Você não faz parte deste projeto." + }, + "ResumeButton": { + "resumeChat": "Continuar Conversa" + } + } + } +} \ No newline at end of file From 86947d8856d6f600e648848e99a40a9c879e8469 Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Mon, 15 Jan 2024 12:44:12 -0300 Subject: [PATCH 09/14] feature: i18n loading from config server translation --- frontend/src/components/molecules/newChatDialog.tsx | 4 +++- frontend/src/main.tsx | 4 +++- frontend/src/state/project.ts | 2 +- frontend/src/state/settings.ts | 6 ++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/molecules/newChatDialog.tsx b/frontend/src/components/molecules/newChatDialog.tsx index 0e6ac0900a..1ad92aaf23 100644 --- a/frontend/src/components/molecules/newChatDialog.tsx +++ b/frontend/src/components/molecules/newChatDialog.tsx @@ -30,7 +30,9 @@ export default function NewChatDialog({ } }} > - {'Create a new chat?'} + + {} + diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 18e1ec85fd..074d6ea74a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,7 +5,9 @@ import { RecoilRoot } from 'recoil'; import './index.css'; -import './i18n'; +import { i18nSetupLocalization } from './i18n'; + +i18nSetupLocalization(); ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/frontend/src/state/project.ts b/frontend/src/state/project.ts index ba72f44c76..b7cc0ffcdf 100644 --- a/frontend/src/state/project.ts +++ b/frontend/src/state/project.ts @@ -18,7 +18,6 @@ export interface IProjectSettings { default_collapse_content?: boolean; default_expand_messages?: boolean; github?: string; - language?: string; }; features: { multi_modal?: boolean; @@ -33,6 +32,7 @@ export interface IProjectSettings { dataPersistence: boolean; threadResumable: boolean; chatProfiles: ChatProfile[]; + translation: object; } export const projectSettingsState = atom({ diff --git a/frontend/src/state/settings.ts b/frontend/src/state/settings.ts index b52fa2a753..34dfc92169 100644 --- a/frontend/src/state/settings.ts +++ b/frontend/src/state/settings.ts @@ -2,8 +2,6 @@ import { atom } from 'recoil'; type ThemeVariant = 'dark' | 'light'; -export type Language = 'en-US' | 'pt-BR'; - const defaultTheme = 'light'; const preferredTheme = localStorage.getItem( @@ -18,7 +16,7 @@ export const defaultSettingsState = { expandAll: false, hideCot: false, isChatHistoryOpen: true, - language: 'en-US' as Language, + language: 'en-US', theme }; @@ -29,7 +27,7 @@ export const settingsState = atom<{ hideCot: boolean; theme: ThemeVariant; isChatHistoryOpen: boolean; - language: Language; + language: string; }>({ key: 'AppSettings', default: defaultSettingsState From 62f9bc1af384e46eadf7f60f8336b2d138361760 Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Mon, 15 Jan 2024 12:45:00 -0300 Subject: [PATCH 10/14] feature: adding translation path to new chat confirmation --- frontend/src/AppWrapper.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/AppWrapper.tsx b/frontend/src/AppWrapper.tsx index 5d863d9f69..220e4351bb 100644 --- a/frontend/src/AppWrapper.tsx +++ b/frontend/src/AppWrapper.tsx @@ -8,7 +8,7 @@ import { useRecoilState, useSetRecoilState } from 'recoil'; import { useApi } from '@chainlit/react-client'; import { IProjectSettings, projectSettingsState } from 'state/project'; -import { Language, settingsState } from 'state/settings'; +import { settingsState } from 'state/settings'; export default function AppWrapper() { const [projectSettings, setProjectSettings] = @@ -18,14 +18,17 @@ export default function AppWrapper() { const { i18n } = useTranslation(); - function handleChangeLanguage(language: string): void { - i18n.changeLanguage(language); + const languageInUse = navigator.language || 'en-US'; + + function handleChangeLanguage(languageBundle: any): void { + i18n.addResourceBundle(languageInUse, 'translation', languageBundle); + i18n.changeLanguage(languageInUse); } const { data } = useApi( apiClient, projectSettings === undefined && isAuthenticated - ? '/project/settings' + ? `/project/settings?language=${languageInUse}` : null ); @@ -45,10 +48,9 @@ export default function AppWrapper() { ...prev, defaultCollapseContent: data.ui.default_collapse_content ?? true, expandAll: !!data.ui.default_expand_messages, - hideCot: !!data.ui.hide_cot, - language: data.ui.language as Language + hideCot: !!data.ui.hide_cot })); - handleChangeLanguage(data.ui.language as Language); + handleChangeLanguage(data.translation); }, [data, setProjectSettings, setAppSettings]); if (!isReady) { From 4d0779ccdab248c885de9d5cb5f7dea3dc5c5a1b Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Mon, 15 Jan 2024 12:48:00 -0300 Subject: [PATCH 11/14] fix: removing language option from cypress test files --- cypress/e2e/ask_multiple_files/.chainlit/config.toml | 3 --- cypress/e2e/chat_profiles/.chainlit/config.toml | 3 --- cypress/e2e/chat_settings/.chainlit/config.toml | 3 --- cypress/e2e/plotly/.chainlit/config.toml | 3 --- cypress/e2e/upload_attachments/.chainlit/config.toml | 3 --- 5 files changed, 15 deletions(-) diff --git a/cypress/e2e/ask_multiple_files/.chainlit/config.toml b/cypress/e2e/ask_multiple_files/.chainlit/config.toml index bc9974deb7..92e7b496fe 100644 --- a/cypress/e2e/ask_multiple_files/.chainlit/config.toml +++ b/cypress/e2e/ask_multiple_files/.chainlit/config.toml @@ -47,9 +47,6 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" -# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". -# language = "en-US" - # Allows user to use speech to text # speech_to_text = true diff --git a/cypress/e2e/chat_profiles/.chainlit/config.toml b/cypress/e2e/chat_profiles/.chainlit/config.toml index 8ef013ff25..f6ae6374f1 100644 --- a/cypress/e2e/chat_profiles/.chainlit/config.toml +++ b/cypress/e2e/chat_profiles/.chainlit/config.toml @@ -59,9 +59,6 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" -# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". -# language = "en-US" - # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" diff --git a/cypress/e2e/chat_settings/.chainlit/config.toml b/cypress/e2e/chat_settings/.chainlit/config.toml index 632177f71b..8c46822f75 100644 --- a/cypress/e2e/chat_settings/.chainlit/config.toml +++ b/cypress/e2e/chat_settings/.chainlit/config.toml @@ -41,9 +41,6 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" -# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". -# language = "en-US" - # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" diff --git a/cypress/e2e/plotly/.chainlit/config.toml b/cypress/e2e/plotly/.chainlit/config.toml index 51728325a5..b59cf19f81 100644 --- a/cypress/e2e/plotly/.chainlit/config.toml +++ b/cypress/e2e/plotly/.chainlit/config.toml @@ -53,9 +53,6 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" -# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". -# language = "en-US" - # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" diff --git a/cypress/e2e/upload_attachments/.chainlit/config.toml b/cypress/e2e/upload_attachments/.chainlit/config.toml index 5ef98c7b73..dd4a16cec1 100644 --- a/cypress/e2e/upload_attachments/.chainlit/config.toml +++ b/cypress/e2e/upload_attachments/.chainlit/config.toml @@ -41,9 +41,6 @@ hide_cot = false # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" -# Select language for the UI. Currently supported languages are "en-US" and "pt-BR". -# language = "en-US" - # Override default MUI light theme. (Check theme.ts) [UI.theme.light] #background = "#FAFAFA" From e763fc1d2b2bf5b2d850aa71d0d3b9239e84babf Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Tue, 16 Jan 2024 09:03:31 -0300 Subject: [PATCH 12/14] feature: translation files in chainlit config created --- backend/chainlit/config.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index 22ef102302..c4c2f63b22 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -21,7 +21,7 @@ BACKEND_ROOT = os.path.dirname(__file__) PACKAGE_ROOT = os.path.dirname(os.path.dirname(BACKEND_ROOT)) -TRANSLATIONS_FOLDER = os.path.join(BACKEND_ROOT, "translations") +TRANSLATIONS_DIR = os.path.join(BACKEND_ROOT, "translations") # Get the directory the script is running from @@ -33,6 +33,7 @@ config_dir = os.path.join(APP_ROOT, ".chainlit") config_file = os.path.join(config_dir, "config.toml") +config_translation_dir = os.path.join(config_dir, "translations") # Default config file created if none exists DEFAULT_CONFIG_STR = f"""[project] @@ -246,10 +247,10 @@ def load_translation(self, language: str): translation = {} translation_lib_file_path = os.path.join( - TRANSLATIONS_FOLDER, f"{language}.json" + config_translation_dir, f"{language}.json" ) default_translation_lib_file_path = os.path.join( - TRANSLATIONS_FOLDER, f"en-US.json" + config_translation_dir, f"en-US.json" ) if os.path.exists(translation_lib_file_path): @@ -272,6 +273,23 @@ def init_config(log=False): elif log: logger.info(f"Config file already exists at {config_file}") + if not os.path.exists(config_translation_dir): + os.makedirs(config_translation_dir, exist_ok=True) + logger.info( + f"Created default translation directory at {config_translation_dir}" + ) + + for file in os.listdir(TRANSLATIONS_DIR): + if file.endswith(".json"): + src = os.path.join(TRANSLATIONS_DIR, file) + dst = os.path.join(config_translation_dir, file) + with open(src, "r", encoding="utf-8") as f: + translation = json.load(f) + if not os.path.exists(dst): + with open(dst, "w", encoding="utf-8") as f: + json.dump(translation, f, indent=4) + logger.info(f"Created default translation file at {dst}") + def load_module(target: str, force_refresh: bool = False): """Load the specified module.""" From 9b5b2fe910725951d2140054772c852c451a406a Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Tue, 16 Jan 2024 09:07:09 -0300 Subject: [PATCH 13/14] feature: adding warning log to default translation usage. --- backend/chainlit/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index c4c2f63b22..0f47ead912 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -257,6 +257,9 @@ def load_translation(self, language: str): with open(translation_lib_file_path, "r", encoding="utf-8") as f: translation = json.load(f) elif os.path.exists(default_translation_lib_file_path): + logger.warning( + f"Translation file for {language} not found. Using default translation." + ) with open(default_translation_lib_file_path, "r", encoding="utf-8") as f: translation = json.load(f) From 99520ae115312d79e29df7f4839586e1650f3ca4 Mon Sep 17 00:00:00 2001 From: DaviReisVieira Date: Tue, 16 Jan 2024 10:04:59 -0300 Subject: [PATCH 14/14] fix: improving function optimization --- backend/chainlit/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index 0f47ead912..80ccdb0a62 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -284,11 +284,11 @@ def init_config(log=False): for file in os.listdir(TRANSLATIONS_DIR): if file.endswith(".json"): - src = os.path.join(TRANSLATIONS_DIR, file) dst = os.path.join(config_translation_dir, file) - with open(src, "r", encoding="utf-8") as f: - translation = json.load(f) - if not os.path.exists(dst): + if not os.path.exists(dst): + src = os.path.join(TRANSLATIONS_DIR, file) + with open(src, "r", encoding="utf-8") as f: + translation = json.load(f) with open(dst, "w", encoding="utf-8") as f: json.dump(translation, f, indent=4) logger.info(f"Created default translation file at {dst}")