diff --git a/frontend/src/InteractiveImport/Movie/SelectMovieModalContent.tsx b/frontend/src/InteractiveImport/Movie/SelectMovieModalContent.tsx
index 7ddaae57d9..7a00c7cf03 100644
--- a/frontend/src/InteractiveImport/Movie/SelectMovieModalContent.tsx
+++ b/frontend/src/InteractiveImport/Movie/SelectMovieModalContent.tsx
@@ -21,6 +21,7 @@ import { scrollDirections } from 'Helpers/Props';
import Movie from 'Movie/Movie';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import dimensions from 'Styles/Variables/dimensions';
+import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import SelectMovieModalTableHeader from './SelectMovieModalTableHeader';
import SelectMovieRow from './SelectMovieRow';
@@ -162,9 +163,7 @@ function SelectMovieModalContent(props: SelectMovieModalContentProps) {
);
const items = useMemo(() => {
- const sorted = [...allMovies].sort((a, b) =>
- a.sortTitle.localeCompare(b.sortTitle)
- );
+ const sorted = [...allMovies].sort(sortByProp('sortTitle'));
return sorted.filter(
(item) =>
diff --git a/frontend/src/Movie/Details/MovieTagsConnector.js b/frontend/src/Movie/Details/MovieTagsConnector.js
index de9a67f2b8..f17e5ab267 100644
--- a/frontend/src/Movie/Details/MovieTagsConnector.js
+++ b/frontend/src/Movie/Details/MovieTagsConnector.js
@@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
+import sortByProp from 'Utilities/Array/sortByProp';
import MovieTags from './MovieTags';
function createMapStateToProps() {
@@ -12,8 +13,8 @@ function createMapStateToProps() {
const tags = movie.tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
- .map((tag) => tag.label)
- .sort((a, b) => a.localeCompare(b));
+ .sort(sortByProp('label'))
+ .map((tag) => tag.label);
return {
tags
diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js
index 8e828620b2..0417d9b211 100644
--- a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js
+++ b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormatsConnector.js
@@ -4,12 +4,12 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cloneCustomFormat, deleteCustomFormat, fetchCustomFormats } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import CustomFormats from './CustomFormats';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.customFormats', sortByName),
+ createSortedSectionSelector('settings.customFormats', sortByProp('name')),
(customFormats) => customFormats
);
}
diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js
index d9e5434691..0dc410fcbc 100644
--- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js
+++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClientsConnector.js
@@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
import { deleteDownloadClient, fetchDownloadClients } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import DownloadClients from './DownloadClients';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.downloadClients', sortByName),
+ createSortedSectionSelector('settings.downloadClients', sortByProp('name')),
createTagsSelector(),
(downloadClients, tagList) => {
return {
diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js b/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js
index 8cd132ab6e..017467e535 100644
--- a/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js
+++ b/frontend/src/Settings/ImportLists/ImportLists/ImportListsConnector.js
@@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { deleteImportList, fetchImportLists } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import ImportLists from './ImportLists';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.importLists', sortByName),
+ createSortedSectionSelector('settings.importLists', sortByProp('name')),
(importLists) => importLists
);
}
diff --git a/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js b/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js
index cb6e830fda..88c571a608 100644
--- a/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js
+++ b/frontend/src/Settings/Indexers/Indexers/IndexersConnector.js
@@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import Indexers from './Indexers';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.indexers', sortByName),
+ createSortedSectionSelector('settings.indexers', sortByProp('name')),
createTagsSelector(),
(indexers, tagList) => {
return {
diff --git a/frontend/src/Settings/Metadata/Metadata/MetadatasConnector.js b/frontend/src/Settings/Metadata/Metadata/MetadatasConnector.js
index fb52ac33b7..8675f4742d 100644
--- a/frontend/src/Settings/Metadata/Metadata/MetadatasConnector.js
+++ b/frontend/src/Settings/Metadata/Metadata/MetadatasConnector.js
@@ -4,12 +4,12 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchMetadata } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import Metadatas from './Metadatas';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.metadata', sortByName),
+ createSortedSectionSelector('settings.metadata', sortByProp('name')),
(metadata) => metadata
);
}
diff --git a/frontend/src/Settings/Notifications/Notifications/NotificationsConnector.js b/frontend/src/Settings/Notifications/Notifications/NotificationsConnector.js
index b306f742ab..6351c6f8ae 100644
--- a/frontend/src/Settings/Notifications/Notifications/NotificationsConnector.js
+++ b/frontend/src/Settings/Notifications/Notifications/NotificationsConnector.js
@@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import Notifications from './Notifications';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.notifications', sortByName),
+ createSortedSectionSelector('settings.notifications', sortByProp('name')),
createTagsSelector(),
(notifications, tagList) => {
return {
diff --git a/frontend/src/Settings/Profiles/Quality/QualityProfileFormatItems.js b/frontend/src/Settings/Profiles/Quality/QualityProfileFormatItems.js
index 7b90dec6c5..61cbefba1b 100644
--- a/frontend/src/Settings/Profiles/Quality/QualityProfileFormatItems.js
+++ b/frontend/src/Settings/Profiles/Quality/QualityProfileFormatItems.js
@@ -20,7 +20,8 @@ function calcOrder(profileFormatItems) {
if (b.score !== a.score) {
return b.score - a.score;
}
- return a.name > b.name ? 1 : -1;
+
+ return a.localeCompare(b.name, undefined, { numeric: true });
}).map((x) => items[x.format]);
}
diff --git a/frontend/src/Settings/Profiles/Quality/QualityProfilesConnector.js b/frontend/src/Settings/Profiles/Quality/QualityProfilesConnector.js
index 354b73e708..3a85f458c9 100644
--- a/frontend/src/Settings/Profiles/Quality/QualityProfilesConnector.js
+++ b/frontend/src/Settings/Profiles/Quality/QualityProfilesConnector.js
@@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
import { fetchMovieCollections } from 'Store/Actions/movieCollectionActions';
import { cloneQualityProfile, deleteQualityProfile, fetchQualityProfiles } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import QualityProfiles from './QualityProfiles';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.qualityProfiles', sortByName),
+ createSortedSectionSelector('settings.qualityProfiles', sortByProp('name')),
(qualityProfiles) => qualityProfiles
);
}
diff --git a/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js b/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js
index 45c8e4b854..f27dc3b5a2 100644
--- a/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js
+++ b/frontend/src/Settings/Tags/AutoTagging/AutoTaggings.js
@@ -9,7 +9,7 @@ import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { cloneAutoTagging, deleteAutoTagging, fetchAutoTaggings } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import AutoTagging from './AutoTagging';
import EditAutoTaggingModal from './EditAutoTaggingModal';
@@ -23,7 +23,7 @@ export default function AutoTaggings() {
isFetching,
isPopulated
} = useSelector(
- createSortedSectionSelector('settings.autoTaggings', sortByName)
+ createSortedSectionSelector('settings.autoTaggings', sortByProp('name'))
);
const tagList = useSelector(createTagsSelector());
diff --git a/frontend/src/Store/Actions/discoverMovieActions.js b/frontend/src/Store/Actions/discoverMovieActions.js
index 0dc53240c4..f113e3d9ee 100644
--- a/frontend/src/Store/Actions/discoverMovieActions.js
+++ b/frontend/src/Store/Actions/discoverMovieActions.js
@@ -4,7 +4,7 @@ import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewMovie from 'Utilities/Movie/getNewMovie';
import getSectionState from 'Utilities/State/getSectionState';
@@ -346,7 +346,7 @@ export const defaultState = {
return acc;
}, []);
- return tagList.sort(sortByName);
+ return tagList.sort(sortByProp('name'));
}
},
{
@@ -365,7 +365,7 @@ export const defaultState = {
return acc;
}, []);
- return collectionList.sort(sortByName);
+ return collectionList.sort(sortByProp('name'));
}
},
{
@@ -384,7 +384,7 @@ export const defaultState = {
return acc;
}, []);
- return collectionList.sort(sortByName);
+ return collectionList.sort(sortByProp('name'));
}
},
{
@@ -426,7 +426,7 @@ export const defaultState = {
return acc;
}, []);
- return tagList.sort(sortByName);
+ return tagList.sort(sortByProp('name'));
}
},
{
diff --git a/frontend/src/Store/Actions/movieCollectionActions.js b/frontend/src/Store/Actions/movieCollectionActions.js
index 8017229af6..7e912b9fd2 100644
--- a/frontend/src/Store/Actions/movieCollectionActions.js
+++ b/frontend/src/Store/Actions/movieCollectionActions.js
@@ -3,7 +3,7 @@ import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewMovie from 'Utilities/Movie/getNewMovie';
import translate from 'Utilities/String/translate';
@@ -155,7 +155,7 @@ export const defaultState = {
return acc;
}, []);
- return genreList.sort(sortByName);
+ return genreList.sort(sortByProp('name'));
}
},
{
diff --git a/frontend/src/Store/Actions/movieIndexActions.js b/frontend/src/Store/Actions/movieIndexActions.js
index 7fc5e8c52a..e2b914a209 100644
--- a/frontend/src/Store/Actions/movieIndexActions.js
+++ b/frontend/src/Store/Actions/movieIndexActions.js
@@ -1,6 +1,6 @@
import { createAction } from 'redux-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import createHandleActions from './Creators/createHandleActions';
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
@@ -327,7 +327,7 @@ export const defaultState = {
return acc;
}, []);
- return collectionList.sort(sortByName);
+ return collectionList.sort(sortByProp('name'));
}
},
{
@@ -349,7 +349,7 @@ export const defaultState = {
return acc;
}, []);
- return groupList.sort(sortByName);
+ return groupList.sort(sortByProp('name'));
}
},
{
@@ -374,7 +374,7 @@ export const defaultState = {
return acc;
}, []);
- return tagList.sort(sortByName);
+ return tagList.sort(sortByProp('name'));
}
},
{
@@ -393,7 +393,7 @@ export const defaultState = {
return acc;
}, []);
- return collectionList.sort(sortByName);
+ return collectionList.sort(sortByProp('name'));
}
},
{
@@ -463,7 +463,7 @@ export const defaultState = {
return acc;
}, []);
- return genreList.sort(sortByName);
+ return genreList.sort(sortByProp('name'));
}
},
{
@@ -512,7 +512,7 @@ export const defaultState = {
return acc;
}, []);
- return certificationList.sort(sortByName);
+ return certificationList.sort(sortByProp('name'));
}
},
{
diff --git a/frontend/src/Store/Actions/releaseActions.js b/frontend/src/Store/Actions/releaseActions.js
index 8c8b151f35..43e9f20e9e 100644
--- a/frontend/src/Store/Actions/releaseActions.js
+++ b/frontend/src/Store/Actions/releaseActions.js
@@ -1,7 +1,7 @@
import { createAction } from 'redux-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
-import sortByName from 'Utilities/Array/sortByName';
+import sortByProp from 'Utilities/Array/sortByProp';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import translate from 'Utilities/String/translate';
import createFetchHandler from './Creators/createFetchHandler';
@@ -198,7 +198,7 @@ export const defaultState = {
return acc;
}, []);
- return genreList.sort(sortByName);
+ return genreList.sort(sortByProp('name'));
}
},
{
diff --git a/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts b/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts
index ac31e5210a..3a581587be 100644
--- a/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts
+++ b/frontend/src/Store/Selectors/createEnabledDownloadClientsSelector.ts
@@ -2,13 +2,17 @@ import { createSelector } from 'reselect';
import { DownloadClientAppState } from 'App/State/SettingsAppState';
import DownloadProtocol from 'DownloadClient/DownloadProtocol';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByName from 'Utilities/Array/sortByName';
+import DownloadClient from 'typings/DownloadClient';
+import sortByProp from 'Utilities/Array/sortByProp';
export default function createEnabledDownloadClientsSelector(
protocol: DownloadProtocol
) {
return createSelector(
- createSortedSectionSelector('settings.downloadClients', sortByName),
+ createSortedSectionSelector
(
+ 'settings.downloadClients',
+ sortByProp('name')
+ ),
(downloadClients: DownloadClientAppState) => {
const { isFetching, isPopulated, error, items } = downloadClients;
diff --git a/frontend/src/Store/Selectors/createRootFoldersSelector.ts b/frontend/src/Store/Selectors/createRootFoldersSelector.ts
index 7e01b57ecc..3eb486191d 100644
--- a/frontend/src/Store/Selectors/createRootFoldersSelector.ts
+++ b/frontend/src/Store/Selectors/createRootFoldersSelector.ts
@@ -2,12 +2,11 @@ import { createSelector } from 'reselect';
import RootFolderAppState from 'App/State/RootFolderAppState';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import RootFolder from 'typings/RootFolder';
+import sortByProp from 'Utilities/Array/sortByProp';
export default function createRootFoldersSelector() {
return createSelector(
- createSortedSectionSelector('rootFolders', (a: RootFolder, b: RootFolder) =>
- a.path.localeCompare(b.path)
- ),
+ createSortedSectionSelector('rootFolders', sortByProp('path')),
(rootFolders: RootFolderAppState) => rootFolders
);
}
diff --git a/frontend/src/Store/Selectors/createSortedSectionSelector.js b/frontend/src/Store/Selectors/createSortedSectionSelector.ts
similarity index 68%
rename from frontend/src/Store/Selectors/createSortedSectionSelector.js
rename to frontend/src/Store/Selectors/createSortedSectionSelector.ts
index 331d890c92..abee01f75e 100644
--- a/frontend/src/Store/Selectors/createSortedSectionSelector.js
+++ b/frontend/src/Store/Selectors/createSortedSectionSelector.ts
@@ -1,14 +1,18 @@
import { createSelector } from 'reselect';
import getSectionState from 'Utilities/State/getSectionState';
-function createSortedSectionSelector(section, comparer) {
+function createSortedSectionSelector(
+ section: string,
+ comparer: (a: T, b: T) => number
+) {
return createSelector(
(state) => state,
(state) => {
const sectionState = getSectionState(state, section, true);
+
return {
...sectionState,
- items: [...sectionState.items].sort(comparer)
+ items: [...sectionState.items].sort(comparer),
};
}
);
diff --git a/frontend/src/System/Tasks/Queued/QueuedTaskRowNameCell.tsx b/frontend/src/System/Tasks/Queued/QueuedTaskRowNameCell.tsx
index 89ff5da1fb..a349e20b74 100644
--- a/frontend/src/System/Tasks/Queued/QueuedTaskRowNameCell.tsx
+++ b/frontend/src/System/Tasks/Queued/QueuedTaskRowNameCell.tsx
@@ -3,6 +3,7 @@ import { useSelector } from 'react-redux';
import { CommandBody } from 'Commands/Command';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import createMultiMoviesSelector from 'Store/Selectors/createMultiMoviesSelector';
+import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import styles from './QueuedTaskRowNameCell.css';
@@ -39,9 +40,7 @@ export default function QueuedTaskRowNameCell(
}
const movies = useSelector(createMultiMoviesSelector(movieIds));
- const sortedMovies = movies.sort((a, b) =>
- a.sortTitle.localeCompare(b.sortTitle)
- );
+ const sortedMovies = movies.sort(sortByProp('sortTitle'));
return (
diff --git a/frontend/src/Utilities/Array/sortByName.js b/frontend/src/Utilities/Array/sortByName.js
deleted file mode 100644
index 1956d3bac3..0000000000
--- a/frontend/src/Utilities/Array/sortByName.js
+++ /dev/null
@@ -1,5 +0,0 @@
-function sortByName(a, b) {
- return a.name.localeCompare(b.name);
-}
-
-export default sortByName;
diff --git a/frontend/src/Utilities/Array/sortByProp.ts b/frontend/src/Utilities/Array/sortByProp.ts
new file mode 100644
index 0000000000..8fbde08c9f
--- /dev/null
+++ b/frontend/src/Utilities/Array/sortByProp.ts
@@ -0,0 +1,13 @@
+import { StringKey } from 'typings/Helpers/KeysMatching';
+
+export function sortByProp<
+ // eslint-disable-next-line no-use-before-define
+ T extends Record,
+ K extends StringKey
+>(sortKey: K) {
+ return (a: T, b: T) => {
+ return a[sortKey].localeCompare(b[sortKey], undefined, { numeric: true });
+ };
+}
+
+export default sortByProp;
diff --git a/frontend/src/typings/Helpers/KeysMatching.ts b/frontend/src/typings/Helpers/KeysMatching.ts
new file mode 100644
index 0000000000..0e20206ef2
--- /dev/null
+++ b/frontend/src/typings/Helpers/KeysMatching.ts
@@ -0,0 +1,7 @@
+type KeysMatching = {
+ [K in keyof T]-?: T[K] extends V ? K : never;
+}[keyof T];
+
+export type StringKey = KeysMatching;
+
+export default KeysMatching;
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index 5e33f065f6..e36a5658f3 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -71,6 +71,7 @@
"AnalyticsEnabledHelpText": "Send anonymous usage and error information to {appName}'s servers. This includes information on your browser, which {appName} WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.",
"Announced": "Announced",
"AnnouncedMsg": "Movie is announced",
+ "Any": "Any",
"ApiKey": "API Key",
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {length} characters long. You can do this via settings or the config file",
"AppDataDirectory": "AppData Directory",