mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
Add a dropdown list in setting to allow change language. (#726)
* init localization * Update defaul to en * Fix merge issue and import path. * Set default to en * Change jsx to tsx * Update the password max length string. * Remove languageContext as using the recoil instead. * Add localization to component endpoints pages * Revert default to en after testing. * Update LoginForm.tsx * Fix translation. * Make lint happy * Merge (#1) * Create deploy.yml * Add localization support for endpoint pages components (#667) * init localization * Update defaul to en * Fix merge issue and import path. * Set default to en * Change jsx to tsx * Update the password max length string. * Remove languageContext as using the recoil instead. * Add localization to component endpoints pages * Revert default to en after testing. * Update LoginForm.tsx * Fix translation. * Make lint happy * Add a restart to melisearch in docker-compose.yml (#684) * Oauth fixes for Cognito (#686) * Add a restart to melisearch in docker-compose.yml * Oauth fixes for Cognito * Use the username or email for full name from oath if not provided --------- Co-authored-by: Donavan <snark@hey.com> * Italian localization support for endpoint (#687) --------- Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com> Co-authored-by: Donavan Stanley <donavan.stanley@gmail.com> Co-authored-by: Donavan <snark@hey.com> Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com> * Translate Nav pages * Fix npm test * Add setting dropdown to change the language * Fix unit test * Use useRecoilState --------- Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com> Co-authored-by: Donavan Stanley <donavan.stanley@gmail.com> Co-authored-by: Donavan <snark@hey.com> Co-authored-by: Marco Beretta <81851188+Berry-13@users.noreply.github.com>
This commit is contained in:
parent
41ed33e792
commit
2faeebfae2
14 changed files with 314 additions and 242 deletions
|
|
@ -31,15 +31,7 @@
|
||||||
{
|
{
|
||||||
"name": "planet",
|
"name": "planet",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["Saturn", "Jupiter", "Mars", "Sun", "Venus", "Mercury", "Moon"],
|
||||||
"Saturn",
|
|
||||||
"Jupiter",
|
|
||||||
"Mars",
|
|
||||||
"Sun",
|
|
||||||
"Venus",
|
|
||||||
"Mercury",
|
|
||||||
"Moon"
|
|
||||||
],
|
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "The planet name to use the corresponding Kamea matrix."
|
"description": "The planet name to use the corresponding Kamea matrix."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { CheckIcon } from 'lucide-react';
|
||||||
import { ThemeContext } from '~/hooks/ThemeContext';
|
import { ThemeContext } from '~/hooks/ThemeContext';
|
||||||
import React, { useState, useContext, useEffect, useCallback } from 'react';
|
import React, { useState, useContext, useEffect, useCallback } from 'react';
|
||||||
import { useClearConversationsMutation } from 'librechat-data-provider';
|
import { useClearConversationsMutation } from 'librechat-data-provider';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue, useRecoilState } from 'recoil';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
import { localize } from '~/localization/Translation';
|
import { localize } from '~/localization/Translation';
|
||||||
|
|
||||||
|
|
@ -66,10 +66,38 @@ export const ClearChatsButton = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LangSelector = ({
|
||||||
|
langcode,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
langcode: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}) => {
|
||||||
|
const lang = useRecoilValue(store.lang);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>{localize(lang, 'com_nav_language')}</div>
|
||||||
|
<select
|
||||||
|
className="w-24 rounded border border-black/10 bg-transparent text-sm dark:border-white/20 dark:bg-gray-900"
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
value={langcode}
|
||||||
|
>
|
||||||
|
<option value="en">{localize(lang, 'com_nav_lang_english')}</option>
|
||||||
|
<option value="cn">{localize(lang, 'com_nav_lang_chinese')}</option>
|
||||||
|
<option value="it">{localize(lang, 'com_nav_lang_italian')}</option>
|
||||||
|
<option value="br">{localize(lang, 'com_nav_lang_brazilian_portuguese')}</option>
|
||||||
|
<option value="es">{localize(lang, 'com_nav_lang_spanish')}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function General() {
|
function General() {
|
||||||
const { theme, setTheme } = useContext(ThemeContext);
|
const { theme, setTheme } = useContext(ThemeContext);
|
||||||
const clearConvosMutation = useClearConversationsMutation();
|
const clearConvosMutation = useClearConversationsMutation();
|
||||||
const [confirmClear, setConfirmClear] = useState(false);
|
const [confirmClear, setConfirmClear] = useState(false);
|
||||||
|
const [langcode, setLangcode] = useRecoilState(store.lang);
|
||||||
const { newConversation } = store.useConversation();
|
const { newConversation } = store.useConversation();
|
||||||
const { refreshConversations } = store.useConversations();
|
const { refreshConversations } = store.useConversations();
|
||||||
|
|
||||||
|
|
@ -97,12 +125,22 @@ function General() {
|
||||||
[setTheme],
|
[setTheme],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const changeLang = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setLangcode(value);
|
||||||
|
},
|
||||||
|
[setLangcode],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs.Content value="general" role="tabpanel" className="w-full md:min-h-[300px]">
|
<Tabs.Content value="general" role="tabpanel" className="w-full md:min-h-[300px]">
|
||||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300">
|
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||||
<ThemeSelector theme={theme} onChange={changeTheme} />
|
<ThemeSelector theme={theme} onChange={changeTheme} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||||
|
<LangSelector langcode={langcode} onChange={changeLang} />
|
||||||
|
</div>
|
||||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||||
<ClearChatsButton confirmClear={confirmClear} onClick={clearConvos} showText={true} />
|
<ClearChatsButton confirmClear={confirmClear} onClick={clearConvos} showText={true} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
36
client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx
Normal file
36
client/src/components/Nav/SettingsTabs/LangSelector.spec.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
|
import { LangSelector } from './General';
|
||||||
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
||||||
|
describe('LangSelector', () => {
|
||||||
|
let mockOnChange;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockOnChange = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { getByText, getByDisplayValue } = render(
|
||||||
|
<RecoilRoot>
|
||||||
|
<LangSelector langcode="en" onChange={mockOnChange} />
|
||||||
|
</RecoilRoot>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByText('Language')).toBeInTheDocument();
|
||||||
|
expect(getByDisplayValue('English')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onChange when the select value changes', () => {
|
||||||
|
const { getByDisplayValue } = render(
|
||||||
|
<RecoilRoot>
|
||||||
|
<LangSelector langcode="en" onChange={mockOnChange} />
|
||||||
|
</RecoilRoot>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(getByDisplayValue('English'), { target: { value: 'it' } });
|
||||||
|
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith('it');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -32,10 +32,10 @@ export const getTranslations = (langCode: string) => {
|
||||||
if (langCode === 'it') {
|
if (langCode === 'it') {
|
||||||
return Italy;
|
return Italy;
|
||||||
}
|
}
|
||||||
if (langCode === 'Br') {
|
if (langCode === 'br') {
|
||||||
return Portuguese;
|
return Portuguese;
|
||||||
}
|
}
|
||||||
if (langCode === 'Es') {
|
if (langCode === 'es') {
|
||||||
return Spanish;
|
return Spanish;
|
||||||
}
|
}
|
||||||
// === add conditionals here for additional languages here === //
|
// === add conditionals here for additional languages here === //
|
||||||
|
|
|
||||||
|
|
@ -183,4 +183,10 @@ export default {
|
||||||
com_nav_settings: 'Settings',
|
com_nav_settings: 'Settings',
|
||||||
com_nav_search_placeholder: 'Search messages',
|
com_nav_search_placeholder: 'Search messages',
|
||||||
com_nav_setting_general: 'General',
|
com_nav_setting_general: 'General',
|
||||||
|
com_nav_language: 'Language',
|
||||||
|
com_nav_lang_english: 'English',
|
||||||
|
com_nav_lang_chinese: '中文',
|
||||||
|
com_nav_lang_italian: 'Italiano',
|
||||||
|
com_nav_lang_brazilian_portuguese: 'Português Brasileiro',
|
||||||
|
com_nav_lang_spanish: 'Español',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue