diff --git a/overseerr-api.yml b/overseerr-api.yml index 460a2a17..81faf737 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -103,8 +103,10 @@ components: properties: apiKey: type: string - example: 'anapikey' readOnly: true + appLanguage: + type: string + example: en applicationTitle: type: string example: Overseerr diff --git a/package.json b/package.json index f052cd90..2cc328f2 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "node-cache": "^5.1.2", "node-schedule": "^2.0.0", "nodemailer": "^6.5.0", - "nookies": "^2.5.2", "openpgp": "^5.0.0-1", "plex-api": "^5.3.1", "pug": "^3.0.2", diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index d718a956..a55b71b3 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -32,6 +32,7 @@ export interface PublicSettingsResponse { cacheImages: boolean; vapidPublic: string; enablePushRegistration: boolean; + locale: string; } export interface CacheItem { diff --git a/server/lib/settings.ts b/server/lib/settings.ts index c2cda61e..178258fb 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -88,6 +88,7 @@ export interface MainSettings { originalLanguage: string; trustProxy: boolean; partialRequestsEnabled: boolean; + locale: string; } interface PublicSettings { @@ -106,6 +107,7 @@ interface FullPublicSettings extends PublicSettings { cacheImages: boolean; vapidPublic: string; enablePushRegistration: boolean; + locale: string; } export interface NotificationAgentConfig { @@ -249,6 +251,7 @@ class Settings { originalLanguage: '', trustProxy: false, partialRequestsEnabled: true, + locale: 'en', }, plex: { name: '', @@ -411,6 +414,7 @@ class Settings { cacheImages: this.data.main.cacheImages, vapidPublic: this.vapidPublic, enablePushRegistration: this.data.notifications.agents.webpush.enabled, + locale: this.data.main.locale, }; } diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 64d693ae..f8f7b9ad 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -5,6 +5,7 @@ import { getSettings } from '../lib/settings'; export const checkUser: Middleware = async (req, _res, next) => { const settings = getSettings(); + if (req.header('X-API-Key') === settings.main.apiKey) { const userRepository = getRepository(User); @@ -28,9 +29,12 @@ export const checkUser: Middleware = async (req, _res, next) => { if (user) { req.user = user; - req.locale = user.settings?.locale; + req.locale = user.settings?.locale + ? user.settings?.locale + : settings.main.locale; } } + next(); }; diff --git a/src/components/Settings/SettingsMain.tsx b/src/components/Settings/SettingsMain.tsx index a7c51be8..03794c3c 100644 --- a/src/components/Settings/SettingsMain.tsx +++ b/src/components/Settings/SettingsMain.tsx @@ -6,7 +6,13 @@ import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; import * as Yup from 'yup'; +import { UserSettingsGeneralResponse } from '../../../server/interfaces/api/userSettingsInterfaces'; import type { MainSettings } from '../../../server/lib/settings'; +import { + availableLanguages, + AvailableLocales, +} from '../../context/LanguageContext'; +import useLocale from '../../hooks/useLocale'; import { Permission, useUser } from '../../hooks/useUser'; import globalMessages from '../../i18n/globalMessages'; import Badge from '../Common/Badge'; @@ -50,15 +56,20 @@ const messages = defineMessages({ validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', partialRequestsEnabled: 'Allow Partial Series Requests', + locale: 'Display Language', }); const SettingsMain: React.FC = () => { const { addToast } = useToasts(); - const { hasPermission: userHasPermission } = useUser(); + const { user: currentUser, hasPermission: userHasPermission } = useUser(); const intl = useIntl(); + const { setLocale } = useLocale(); const { data, error, revalidate } = useSWR( '/api/v1/settings/main' ); + const { data: userData } = useSWR( + currentUser ? `/api/v1/user/${currentUser.id}/settings/main` : null + ); const MainSettingsSchema = Yup.object().shape({ applicationTitle: Yup.string().required( @@ -122,6 +133,7 @@ const SettingsMain: React.FC = () => { applicationUrl: data?.applicationUrl, csrfProtection: data?.csrfProtection, hideAvailable: data?.hideAvailable, + locale: data?.locale ?? 'en', region: data?.region, originalLanguage: data?.originalLanguage, partialRequestsEnabled: data?.partialRequestsEnabled, @@ -136,6 +148,7 @@ const SettingsMain: React.FC = () => { applicationUrl: values.applicationUrl, csrfProtection: values.csrfProtection, hideAvailable: values.hideAvailable, + locale: values.locale, region: values.region, originalLanguage: values.originalLanguage, partialRequestsEnabled: values.partialRequestsEnabled, @@ -143,6 +156,14 @@ const SettingsMain: React.FC = () => { }); mutate('/api/v1/settings/public'); + if (setLocale) { + setLocale( + (userData?.locale + ? userData.locale + : values.locale) as AvailableLocales + ); + } + addToast(intl.formatMessage(messages.toastSettingsSuccess), { autoDismiss: true, appearance: 'success', @@ -271,6 +292,28 @@ const SettingsMain: React.FC = () => { /> +
+ +
+
+ + {(Object.keys( + availableLanguages + ) as (keyof typeof availableLanguages)[]).map((key) => ( + + ))} + +
+
+