diff --git a/api/package.json b/api/package.json
index 6f4861bf5..43ccda33e 100644
--- a/api/package.json
+++ b/api/package.json
@@ -49,6 +49,7 @@
"compression": "^1.7.4",
"connect-redis": "^7.1.0",
"cookie": "^0.5.0",
+ "cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dedent": "^1.5.3",
"dotenv": "^16.0.3",
diff --git a/api/server/index.js b/api/server/index.js
index b39116fbb..3cb969ddc 100644
--- a/api/server/index.js
+++ b/api/server/index.js
@@ -7,6 +7,8 @@ const express = require('express');
const compression = require('compression');
const passport = require('passport');
const mongoSanitize = require('express-mongo-sanitize');
+const fs = require('fs');
+const cookieParser = require('cookie-parser');
const { jwtLogin, passportLogin } = require('~/strategies');
const { connectDb, indexSync } = require('~/lib/db');
const { isEnabled } = require('~/server/utils');
@@ -37,6 +39,9 @@ const startServer = async () => {
app.disable('x-powered-by');
await AppService(app);
+ const indexPath = path.join(app.locals.paths.dist, 'index.html');
+ const indexHTML = fs.readFileSync(indexPath, 'utf8');
+
app.get('/health', (_req, res) => res.status(200).send('OK'));
/* Middleware */
@@ -50,6 +55,7 @@ const startServer = async () => {
app.use(staticCache(app.locals.paths.assets));
app.set('trust proxy', 1); /* trust first proxy */
app.use(cors());
+ app.use(cookieParser());
if (!isEnabled(DISABLE_COMPRESSION)) {
app.use(compression());
@@ -101,8 +107,12 @@ const startServer = async () => {
app.use('/api/roles', routes.roles);
app.use('/api/tags', routes.tags);
+
app.use((req, res) => {
- res.sendFile(path.join(app.locals.paths.dist, 'index.html'));
+ // Replace lang attribute in index.html with lang from cookies or accept-language header
+ const lang = req.cookies.lang || req.headers['accept-language']?.split(',')[0] || 'en-US';
+ const updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, `lang="${lang}"`);
+ res.send(updatedIndexHtml);
});
app.listen(port, host, () => {
diff --git a/client/index.html b/client/index.html
index 0523894cf..e70cc7678 100644
--- a/client/index.html
+++ b/client/index.html
@@ -22,7 +22,7 @@
type="image/png"
sizes="16x16"
href="/assets/favicon-16x16.png"
- />
+ />
{
- setSelectedLang(value);
+ let userLang = value;
if (value === 'auto') {
- const userLang = navigator.language || navigator.languages[0];
- setLangcode(userLang);
- localStorage.setItem('lang', userLang);
- } else {
- setLangcode(value);
- localStorage.setItem('lang', value);
+ userLang = navigator.language || navigator.languages[0];
}
+
+ requestAnimationFrame(() => {
+ document.documentElement.lang = userLang;
+ });
+ setLangcode(userLang);
+ Cookies.set('lang', userLang, { expires: 365 });
},
- [setLangcode, setSelectedLang],
+ [setLangcode],
);
return (
@@ -161,7 +162,7 @@ function General() {
-
+
diff --git a/client/src/store/language.ts b/client/src/store/language.ts
index 0df191fb3..50e98fb12 100644
--- a/client/src/store/language.ts
+++ b/client/src/store/language.ts
@@ -1,10 +1,11 @@
-import { atom } from 'recoil';
+import Cookies from 'js-cookie';
+import { atomWithLocalStorage } from './utils';
-const userLang = navigator.language || navigator.languages[0];
+const defaultLang = () => {
+ const userLang = navigator.language || navigator.languages[0];
+ return Cookies.get('lang') || localStorage.getItem('lang') || userLang;
+};
-const lang = atom({
- key: 'lang',
- default: localStorage.getItem('lang') || userLang,
-});
+const lang = atomWithLocalStorage('lang', defaultLang());
export default { lang };
diff --git a/package-lock.json b/package-lock.json
index c683624b5..d54fa86a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,6 +58,7 @@
"compression": "^1.7.4",
"connect-redis": "^7.1.0",
"cookie": "^0.5.0",
+ "cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dedent": "^1.5.3",
"dotenv": "^16.0.3",
@@ -1156,6 +1157,7 @@
"filenamify": "^6.0.0",
"html-to-image": "^1.11.11",
"image-blob-reduce": "^4.1.0",
+ "js-cookie": "^3.0.5",
"librechat-data-provider": "*",
"lodash": "^4.17.21",
"lucide-react": "^0.394.0",
@@ -1202,6 +1204,7 @@
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.2",
+ "@types/js-cookie": "^3.0.6",
"@types/node": "^20.3.0",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
@@ -14452,6 +14455,12 @@
"pretty-format": "^29.0.0"
}
},
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true
+ },
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
@@ -17266,6 +17275,26 @@
"node": ">= 0.6"
}
},
+ "node_modules/cookie-parser": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+ "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+ "dependencies": {
+ "cookie": "0.4.1",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -23346,6 +23375,14 @@
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="
},
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/js-tiktoken": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.10.tgz",