1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2025-11-06 09:19:38 +02:00

New: Add UI Localization Framework

This commit is contained in:
Michael Casey
2021-08-10 07:13:33 +10:00
committed by Mark McDowall
parent 1977f4aa3c
commit 5938a95abb
17 changed files with 557 additions and 71 deletions

View File

@@ -5,25 +5,14 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import { fetchLanguageProfileSchema } from 'Store/Actions/settingsActions';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import SelectLanguageModalContent from './SelectLanguageModalContent';
function createMapStateToProps() {
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() : []
};
createLanguagesSelector(),
(languages) => {
return languages;
}
);
}

View File

@@ -12,6 +12,7 @@ import { inputTypes } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import themes from 'Styles/Themes';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
export const firstDayOfWeekOptions = [
{ key: 0, value: 'Sunday' },
@@ -57,6 +58,7 @@ class UISettings extends Component {
hasSettings,
onInputChange,
onSavePress,
languages,
...otherProps
} = this.props;
@@ -72,17 +74,19 @@ class UISettings extends Component {
<PageContentBody>
{
isFetching &&
<LoadingIndicator />
isFetching ?
<LoadingIndicator /> :
null
}
{
!isFetching && error &&
<div>Unable to load UI settings</div>
!isFetching && error ?
<div>Unable to load UI settings</div> :
null
}
{
hasSettings && !isFetching && !error &&
hasSettings && !isFetching && !error ?
<Form
id="uiSettings"
{...otherProps}
@@ -191,7 +195,23 @@ class UISettings extends Component {
/>
</FormGroup>
</FieldSet>
</Form>
<FieldSet legend={translate('Language')}>
<FormGroup>
<FormLabel>{translate('UI Language')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="uiLanguage"
values={languages}
helpText={translate('Language that Sonarr will use for UI')}
helpTextWarning={translate('Browser Reload Required')}
onChange={onInputChange}
{...settings.uiLanguage}
/>
</FormGroup>
</FieldSet>
</Form> :
null
}
</PageContentBody>
</PageContent>
@@ -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
};

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}