mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
fix: correctly fall back to English name in LanguageSelector (#1537)
* fix: correctly fall back to English name in LanguageSelector * refactor: clean up language sort & name logic * refactor: also clean up region sort & name logic * refactor: use arrow functions
This commit is contained in:
@@ -14,6 +14,7 @@ export interface Library {
|
||||
export interface Region {
|
||||
iso_3166_1: string;
|
||||
english_name: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface Language {
|
||||
|
@@ -1,8 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { sortBy } from 'lodash';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import type { OptionsType, OptionTypeBase } from 'react-select';
|
||||
import useSWR from 'swr';
|
||||
import { Language } from '../../../server/lib/settings';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
|
||||
@@ -29,7 +31,6 @@ const selectStyles = {
|
||||
};
|
||||
|
||||
interface LanguageSelectorProps {
|
||||
languages: Language[];
|
||||
value?: string;
|
||||
setFieldValue: (property: string, value: string) => void;
|
||||
serverValue?: string;
|
||||
@@ -37,26 +38,33 @@ interface LanguageSelectorProps {
|
||||
}
|
||||
|
||||
const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
||||
languages,
|
||||
value,
|
||||
setFieldValue,
|
||||
serverValue,
|
||||
isUserSettings = false,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { data: languages } = useSWR<Language[]>('/api/v1/languages');
|
||||
|
||||
const defaultLanguageNameFallback = serverValue
|
||||
? languages.find((language) => language.iso_639_1 === serverValue)
|
||||
?.english_name ?? serverValue
|
||||
: undefined;
|
||||
|
||||
const options: OptionType[] =
|
||||
languages.map((language) => ({
|
||||
label:
|
||||
const sortedLanguages = useMemo(() => {
|
||||
languages?.forEach((language) => {
|
||||
language.name =
|
||||
intl.formatDisplayName(language.iso_639_1, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? language.english_name,
|
||||
}) ?? language.english_name;
|
||||
});
|
||||
|
||||
return sortBy(languages, 'name');
|
||||
}, [intl, languages]);
|
||||
|
||||
const languageName = (languageCode: string) =>
|
||||
sortedLanguages?.find((language) => language.iso_639_1 === languageCode)
|
||||
?.name ?? languageCode;
|
||||
|
||||
const options: OptionType[] =
|
||||
sortedLanguages?.map((language) => ({
|
||||
label: language.name,
|
||||
value: language.iso_639_1,
|
||||
})) ?? [];
|
||||
|
||||
@@ -67,13 +75,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
||||
language: serverValue
|
||||
? serverValue
|
||||
.split('|')
|
||||
.map(
|
||||
(value) =>
|
||||
intl.formatDisplayName(value, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? defaultLanguageNameFallback
|
||||
)
|
||||
.map((value) => languageName(value))
|
||||
.reduce((prev, curr) =>
|
||||
intl.formatMessage(globalMessages.delimitedlist, {
|
||||
a: prev,
|
||||
@@ -112,13 +114,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
||||
language: serverValue
|
||||
? serverValue
|
||||
.split('|')
|
||||
.map(
|
||||
(value) =>
|
||||
intl.formatDisplayName(value, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? defaultLanguageNameFallback
|
||||
)
|
||||
.map((value) => languageName(value))
|
||||
.reduce((prev, curr) =>
|
||||
intl.formatMessage(globalMessages.delimitedlist, {
|
||||
a: prev,
|
||||
@@ -130,7 +126,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
||||
isFixed: true,
|
||||
}
|
||||
: value?.split('|').map((code) => {
|
||||
const matchedLanguage = languages.find(
|
||||
const matchedLanguage = sortedLanguages?.find(
|
||||
(lang) => lang.iso_639_1 === code
|
||||
);
|
||||
|
||||
@@ -139,11 +135,7 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
|
||||
}
|
||||
|
||||
return {
|
||||
label:
|
||||
intl.formatDisplayName(matchedLanguage.iso_639_1, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? matchedLanguage.english_name,
|
||||
label: matchedLanguage.name,
|
||||
value: matchedLanguage.iso_639_1,
|
||||
};
|
||||
}) ?? undefined
|
||||
|
@@ -2,6 +2,7 @@ import { Listbox, Transition } from '@headlessui/react';
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/solid';
|
||||
import { hasFlag } from 'country-flag-icons';
|
||||
import 'country-flag-icons/3x2/flags.css';
|
||||
import { sortBy } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
@@ -39,32 +40,21 @@ const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const sortedRegions = useMemo(
|
||||
() =>
|
||||
regions?.sort((region1, region2) => {
|
||||
const region1Name =
|
||||
intl.formatDisplayName(region1.iso_3166_1, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? region1.english_name;
|
||||
const region2Name =
|
||||
intl.formatDisplayName(region2.iso_3166_1, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? region2.english_name;
|
||||
const sortedRegions = useMemo(() => {
|
||||
regions?.forEach((region) => {
|
||||
region.name =
|
||||
intl.formatDisplayName(region.iso_3166_1, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? region.english_name;
|
||||
});
|
||||
|
||||
return region1Name === region2Name
|
||||
? 0
|
||||
: region1Name > region2Name
|
||||
? 1
|
||||
: -1;
|
||||
}),
|
||||
[intl, regions]
|
||||
);
|
||||
return sortBy(regions, 'name');
|
||||
}, [intl, regions]);
|
||||
|
||||
const defaultRegionNameFallback =
|
||||
regions?.find((region) => region.iso_3166_1 === currentSettings.region)
|
||||
?.english_name ?? currentSettings.region;
|
||||
const regionName = (regionCode: string) =>
|
||||
sortedRegions?.find((region) => region.iso_3166_1 === regionCode)?.name ??
|
||||
regionCode;
|
||||
|
||||
useEffect(() => {
|
||||
if (regions && value) {
|
||||
@@ -110,17 +100,11 @@ const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
)}
|
||||
<span className="block truncate">
|
||||
{selectedRegion && selectedRegion.iso_3166_1 !== 'all'
|
||||
? intl.formatDisplayName(selectedRegion.iso_3166_1, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? selectedRegion.english_name
|
||||
? regionName(selectedRegion.iso_3166_1)
|
||||
: isUserSetting && selectedRegion?.iso_3166_1 !== 'all'
|
||||
? intl.formatMessage(messages.regionServerDefault, {
|
||||
region: currentSettings.region
|
||||
? intl.formatDisplayName(currentSettings.region, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? defaultRegionNameFallback
|
||||
? regionName(currentSettings.region)
|
||||
: intl.formatMessage(messages.regionDefault),
|
||||
})
|
||||
: intl.formatMessage(messages.regionDefault)}
|
||||
@@ -168,13 +152,7 @@ const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
>
|
||||
{intl.formatMessage(messages.regionServerDefault, {
|
||||
region: currentSettings.region
|
||||
? intl.formatDisplayName(
|
||||
currentSettings.region,
|
||||
{
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}
|
||||
) ?? defaultRegionNameFallback
|
||||
? regionName(currentSettings.region)
|
||||
: intl.formatMessage(messages.regionDefault),
|
||||
})}
|
||||
</span>
|
||||
@@ -241,10 +219,7 @@ const RegionSelector: React.FC<RegionSelectorProps> = ({
|
||||
selected ? 'font-semibold' : 'font-normal'
|
||||
} block truncate`}
|
||||
>
|
||||
{intl.formatDisplayName(region.iso_3166_1, {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
}) ?? region.english_name}
|
||||
{regionName(region.iso_3166_1)}
|
||||
</span>
|
||||
{selected && (
|
||||
<span
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { RefreshIcon } from '@heroicons/react/solid';
|
||||
import axios from 'axios';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import * as Yup from 'yup';
|
||||
import type { Language, MainSettings } from '../../../server/lib/settings';
|
||||
import type { MainSettings } from '../../../server/lib/settings';
|
||||
import { Permission, useUser } from '../../hooks/useUser';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import Badge from '../Common/Badge';
|
||||
@@ -58,9 +58,7 @@ const SettingsMain: React.FC = () => {
|
||||
const { data, error, revalidate } = useSWR<MainSettings>(
|
||||
'/api/v1/settings/main'
|
||||
);
|
||||
const { data: languages, error: languagesError } = useSWR<Language[]>(
|
||||
'/api/v1/languages'
|
||||
);
|
||||
|
||||
const MainSettingsSchema = Yup.object().shape({
|
||||
applicationTitle: Yup.string().required(
|
||||
intl.formatMessage(messages.validationApplicationTitle)
|
||||
@@ -96,26 +94,7 @@ const SettingsMain: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const sortedLanguages = useMemo(
|
||||
() =>
|
||||
languages?.sort((lang1, lang2) => {
|
||||
const lang1Name =
|
||||
intl.formatDisplayName(lang1.iso_639_1, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? lang1.english_name;
|
||||
const lang2Name =
|
||||
intl.formatDisplayName(lang2.iso_639_1, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? lang2.english_name;
|
||||
|
||||
return lang1Name === lang2Name ? 0 : lang1Name > lang2Name ? 1 : -1;
|
||||
}),
|
||||
[intl, languages]
|
||||
);
|
||||
|
||||
if (!data && !error && !languages && !languagesError) {
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
@@ -316,7 +295,6 @@ const SettingsMain: React.FC = () => {
|
||||
<div className="form-input">
|
||||
<div className="form-input-field">
|
||||
<LanguageSelector
|
||||
languages={sortedLanguages ?? []}
|
||||
setFieldValue={setFieldValue}
|
||||
value={values.originalLanguage}
|
||||
/>
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import axios from 'axios';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import { UserSettingsGeneralResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
|
||||
import { Language } from '../../../../../server/lib/settings';
|
||||
import {
|
||||
availableLanguages,
|
||||
AvailableLocales,
|
||||
@@ -72,38 +71,11 @@ const UserGeneralSettings: React.FC = () => {
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
const { data: languages, error: languagesError } = useSWR<Language[]>(
|
||||
'/api/v1/languages'
|
||||
);
|
||||
|
||||
const sortedLanguages = useMemo(
|
||||
() =>
|
||||
languages?.sort((lang1, lang2) => {
|
||||
const lang1Name =
|
||||
intl.formatDisplayName(lang1.iso_639_1, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? lang1.english_name;
|
||||
const lang2Name =
|
||||
intl.formatDisplayName(lang2.iso_639_1, {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
}) ?? lang2.english_name;
|
||||
|
||||
return lang1Name === lang2Name ? 0 : lang1Name > lang2Name ? 1 : -1;
|
||||
}),
|
||||
[intl, languages]
|
||||
);
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (!languages && !languagesError) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (!data || !languages) {
|
||||
if (!data) {
|
||||
return <Error statusCode={500} />;
|
||||
}
|
||||
|
||||
@@ -263,7 +235,6 @@ const UserGeneralSettings: React.FC = () => {
|
||||
<div className="form-input">
|
||||
<div className="form-input-field">
|
||||
<LanguageSelector
|
||||
languages={sortedLanguages ?? []}
|
||||
setFieldValue={setFieldValue}
|
||||
serverValue={currentSettings.originalLanguage}
|
||||
value={values.originalLanguage}
|
||||
|
Reference in New Issue
Block a user