{
- isFetching &&
-
+ isFetching ?
+ :
+ null
}
{
- !isFetching && error &&
- Unable to load UI settings
+ !isFetching && error ?
+ Unable to load UI settings
:
+ null
}
{
- hasSettings && !isFetching && !error &&
+ hasSettings && !isFetching && !error ?
-
+
+
+ :
+ null
}
@@ -205,6 +225,7 @@ UISettings.propTypes = {
error: PropTypes.object,
settings: PropTypes.object.isRequired,
hasSettings: PropTypes.bool.isRequired,
+ languages: PropTypes.arrayOf(PropTypes.object).isRequired,
onSavePress: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired
};
diff --git a/frontend/src/Settings/UI/UISettingsConnector.js b/frontend/src/Settings/UI/UISettingsConnector.js
index b0fbb3339..7b7846415 100644
--- a/frontend/src/Settings/UI/UISettingsConnector.js
+++ b/frontend/src/Settings/UI/UISettingsConnector.js
@@ -3,30 +3,63 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearPendingChanges } from 'Store/Actions/baseActions';
-import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
+import { fetchLanguageProfileSchema, fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
+import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import UISettings from './UISettings';
const SECTION = 'ui';
+const FILTER_LANGUAGES = ['Any', 'Unknown'];
+
+function createFilteredLanguagesSelector() {
+ return createSelector(
+ createLanguagesSelector(),
+ (languages) => {
+ if (!languages || !languages.items) {
+ return [];
+ }
+
+ const newItems = languages.items
+ .filter((lang) => !FILTER_LANGUAGES.includes(lang.language.name))
+ .map((item) => {
+ return {
+ key: item.language.id,
+ value: item.language.name
+ };
+ });
+
+ return {
+ ...languages,
+ items: newItems
+ };
+ }
+ );
+}
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createSettingsSectionSelector(SECTION),
- (advancedSettings, sectionSettings) => {
+ createFilteredLanguagesSelector(),
+ (advancedSettings, sectionSettings, languages) => {
return {
advancedSettings,
- ...sectionSettings
+ languages: languages.items,
+ isLanguagesPopulated: languages.isPopulated,
+ ...sectionSettings,
+ isFetching: sectionSettings.isFetching || languages.isFetching,
+ error: sectionSettings.error || languages.error
};
}
);
}
const mapDispatchToProps = {
- setUISettingsValue,
- saveUISettings,
- fetchUISettings,
- clearPendingChanges
+ dispatchSetUISettingsValue: setUISettingsValue,
+ dispatchSaveUISettings: saveUISettings,
+ dispatchFetchUISettings: fetchUISettings,
+ dispatchClearPendingChanges: clearPendingChanges,
+ dispatchFetchLanguageProfileSchema: fetchLanguageProfileSchema
};
class UISettingsConnector extends Component {
@@ -35,22 +68,32 @@ class UISettingsConnector extends Component {
// Lifecycle
componentDidMount() {
- this.props.fetchUISettings();
+ const {
+ isLanguagesPopulated,
+ dispatchFetchUISettings,
+ dispatchFetchLanguageProfileSchema
+ } = this.props;
+
+ dispatchFetchUISettings();
+
+ if (!isLanguagesPopulated) {
+ dispatchFetchLanguageProfileSchema();
+ }
}
componentWillUnmount() {
- this.props.clearPendingChanges({ section: `settings.${SECTION}` });
+ this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
}
//
// Listeners
onInputChange = ({ name, value }) => {
- this.props.setUISettingsValue({ name, value });
+ this.props.dispatchSetUISettingsValue({ name, value });
};
onSavePress = () => {
- this.props.saveUISettings();
+ this.props.dispatchSaveUISettings();
};
//
@@ -68,10 +111,12 @@ class UISettingsConnector extends Component {
}
UISettingsConnector.propTypes = {
- setUISettingsValue: PropTypes.func.isRequired,
- saveUISettings: PropTypes.func.isRequired,
- fetchUISettings: PropTypes.func.isRequired,
- clearPendingChanges: PropTypes.func.isRequired
+ isLanguagesPopulated: PropTypes.bool.isRequired,
+ dispatchSetUISettingsValue: PropTypes.func.isRequired,
+ dispatchSaveUISettings: PropTypes.func.isRequired,
+ dispatchFetchUISettings: PropTypes.func.isRequired,
+ dispatchClearPendingChanges: PropTypes.func.isRequired,
+ dispatchFetchLanguageProfileSchema: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(UISettingsConnector);
diff --git a/frontend/src/Store/Selectors/createLanguagesSelector.js b/frontend/src/Store/Selectors/createLanguagesSelector.js
new file mode 100644
index 000000000..53de7d696
--- /dev/null
+++ b/frontend/src/Store/Selectors/createLanguagesSelector.js
@@ -0,0 +1,24 @@
+import { createSelector } from 'reselect';
+
+function createLanguagesSelector() {
+ return createSelector(
+ (state) => state.settings.languageProfiles,
+ (languageProfiles) => {
+ const {
+ isSchemaFetching: isFetching,
+ isSchemaPopulated: isPopulated,
+ schemaError: error,
+ schema
+ } = languageProfiles;
+
+ return {
+ isFetching,
+ isPopulated,
+ error,
+ items: schema.languages ? [...schema.languages].reverse() : []
+ };
+ }
+ );
+}
+
+export default createLanguagesSelector;
diff --git a/frontend/src/Utilities/String/translate.js b/frontend/src/Utilities/String/translate.js
new file mode 100644
index 000000000..cef8ce047
--- /dev/null
+++ b/frontend/src/Utilities/String/translate.js
@@ -0,0 +1,28 @@
+import createAjaxRequest from 'Utilities/createAjaxRequest';
+
+function getTranslations() {
+ return createAjaxRequest({
+ global: false,
+ dataType: 'json',
+ url: '/localization'
+ }).request;
+}
+
+let translations = {};
+
+getTranslations().then((data) => {
+ translations = data.strings;
+});
+
+export default function translate(key, tokens) {
+ const translation = translations[key] || key;
+
+ if (tokens) {
+ return translation.replace(
+ /\{([a-z0-9]+?)\}/gi,
+ (match, tokenMatch) => String(tokens[tokenMatch]) ?? match
+ );
+ }
+
+ return translation;
+}
diff --git a/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs b/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs
new file mode 100644
index 000000000..79056ca81
--- /dev/null
+++ b/src/NzbDrone.Core.Test/Localization/LocalizationServiceFixture.cs
@@ -0,0 +1,88 @@
+using System;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Common.EnvironmentInfo;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.Languages;
+using NzbDrone.Core.Localization;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.Localization
+{
+ [TestFixture]
+ public class LocalizationServiceFixture : CoreTest