New: Localization Framework

This commit is contained in:
Qstick 2021-10-01 20:03:57 -05:00
parent 99ccaab6a6
commit 729a876fc7
23 changed files with 811 additions and 32 deletions

View file

@ -6,7 +6,7 @@ import { createSelector } from 'reselect';
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchArtist } from 'Store/Actions/artistActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchImportLists, fetchMetadataProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchImportLists, fetchLanguages, fetchMetadataProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
@ -46,6 +46,7 @@ const selectIsPopulated = createSelector(
(state) => state.customFilters.isPopulated,
(state) => state.tags.isPopulated,
(state) => state.settings.ui.isPopulated,
(state) => state.settings.languages.isPopulated,
(state) => state.settings.qualityProfiles.isPopulated,
(state) => state.settings.metadataProfiles.isPopulated,
(state) => state.settings.importLists.isPopulated,
@ -54,6 +55,7 @@ const selectIsPopulated = createSelector(
customFiltersIsPopulated,
tagsIsPopulated,
uiSettingsIsPopulated,
languagesIsPopulated,
qualityProfilesIsPopulated,
metadataProfilesIsPopulated,
importListsIsPopulated,
@ -63,6 +65,7 @@ const selectIsPopulated = createSelector(
customFiltersIsPopulated &&
tagsIsPopulated &&
uiSettingsIsPopulated &&
languagesIsPopulated &&
qualityProfilesIsPopulated &&
metadataProfilesIsPopulated &&
importListsIsPopulated &&
@ -75,6 +78,7 @@ const selectErrors = createSelector(
(state) => state.customFilters.error,
(state) => state.tags.error,
(state) => state.settings.ui.error,
(state) => state.settings.languages.error,
(state) => state.settings.qualityProfiles.error,
(state) => state.settings.metadataProfiles.error,
(state) => state.settings.importLists.error,
@ -83,6 +87,7 @@ const selectErrors = createSelector(
customFiltersError,
tagsError,
uiSettingsError,
languagesError,
qualityProfilesError,
metadataProfilesError,
importListsError,
@ -92,6 +97,7 @@ const selectErrors = createSelector(
customFiltersError ||
tagsError ||
uiSettingsError ||
languagesError ||
qualityProfilesError ||
metadataProfilesError ||
importListsError ||
@ -103,6 +109,7 @@ const selectErrors = createSelector(
customFiltersError,
tagsError,
uiSettingsError,
languagesError,
qualityProfilesError,
metadataProfilesError,
importListsError,
@ -147,6 +154,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchTags() {
dispatch(fetchTags());
},
dispatchFetchLanguages() {
dispatch(fetchLanguages());
},
dispatchFetchQualityProfiles() {
dispatch(fetchQualityProfiles());
},
@ -189,6 +199,7 @@ class PageConnector extends Component {
this.props.dispatchFetchArtist();
this.props.dispatchFetchCustomFilters();
this.props.dispatchFetchTags();
this.props.dispatchFetchLanguages();
this.props.dispatchFetchQualityProfiles();
this.props.dispatchFetchMetadataProfiles();
this.props.dispatchFetchImportLists();
@ -213,6 +224,7 @@ class PageConnector extends Component {
hasError,
dispatchFetchArtist,
dispatchFetchTags,
dispatchFetchLanguages,
dispatchFetchQualityProfiles,
dispatchFetchMetadataProfiles,
dispatchFetchImportLists,
@ -252,6 +264,7 @@ PageConnector.propTypes = {
dispatchFetchArtist: PropTypes.func.isRequired,
dispatchFetchCustomFilters: PropTypes.func.isRequired,
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchLanguages: PropTypes.func.isRequired,
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
dispatchFetchMetadataProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired,

View file

@ -10,6 +10,7 @@ import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { inputTypes } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import styles from './UISettings.css';
export const firstDayOfWeekOptions = [
@ -56,9 +57,12 @@ class UISettings extends Component {
hasSettings,
onInputChange,
onSavePress,
languages,
...otherProps
} = this.props;
const uiLanguages = languages.filter((item) => item.value !== 'Original');
return (
<PageContent title="UI Settings">
<SettingsToolbarConnector
@ -220,6 +224,20 @@ class UISettings extends Component {
</div>
</FormGroup>
</FieldSet>
<FieldSet legend={translate('Language')}>
<FormGroup>
<FormLabel>{translate('UILanguage')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="uiLanguage"
values={uiLanguages}
helpText={translate('UILanguageHelpText')}
helpTextWarning={translate('UILanguageHelpTextWarning')}
onChange={onInputChange}
{...settings.uiLanguage}
/>
</FormGroup>
</FieldSet>
</Form>
}
</PageContentBody>
@ -235,6 +253,7 @@ UISettings.propTypes = {
settings: PropTypes.object.isRequired,
hasSettings: PropTypes.bool.isRequired,
onSavePress: PropTypes.func.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
onInputChange: PropTypes.func.isRequired
};

View file

@ -9,13 +9,38 @@ import UISettings from './UISettings';
const SECTION = 'ui';
function createLanguagesSelector() {
return createSelector(
(state) => state.settings.languages,
(languages) => {
const items = languages.items;
const filterItems = ['Any', 'Unknown'];
if (!items) {
return [];
}
const newItems = items.filter((lang) => !filterItems.includes(lang.name)).map((item) => {
return {
key: item.id,
value: item.name
};
});
return newItems;
}
);
}
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createSettingsSectionSelector(SECTION),
(advancedSettings, sectionSettings) => {
createLanguagesSelector(),
(advancedSettings, sectionSettings, languages) => {
return {
advancedSettings,
languages,
...sectionSettings
};
}

View file

@ -0,0 +1,48 @@
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import { createThunk } from 'Store/thunks';
//
// Variables
const section = 'settings.languages';
//
// Actions Types
export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages';
//
// Action Creators
export const fetchLanguages = createThunk(FETCH_LANGUAGES);
//
// Details
export default {
//
// State
defaultState: {
isFetching: false,
isPopulated: false,
error: null,
items: []
},
//
// Action Handlers
actionHandlers: {
[FETCH_LANGUAGES]: createFetchHandler(section, '/language')
},
//
// Reducers
reducers: {
}
};

View file

@ -9,6 +9,7 @@ import importListExclusions from './Settings/importListExclusions';
import importLists from './Settings/importLists';
import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languages from './Settings/languages';
import mediaManagement from './Settings/mediaManagement';
import metadata from './Settings/metadata';
import metadataProfiles from './Settings/metadataProfiles';
@ -31,6 +32,7 @@ export * from './Settings/importLists';
export * from './Settings/importListExclusions';
export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/languages';
export * from './Settings/metadataProfiles';
export * from './Settings/mediaManagement';
export * from './Settings/metadata';
@ -64,6 +66,7 @@ export const defaultState = {
indexers: indexers.defaultState,
importLists: importLists.defaultState,
importListExclusions: importListExclusions.defaultState,
languages: languages.defaultState,
metadataProfiles: metadataProfiles.defaultState,
mediaManagement: mediaManagement.defaultState,
metadata: metadata.defaultState,
@ -105,6 +108,7 @@ export const actionHandlers = handleThunks({
...indexers.actionHandlers,
...importLists.actionHandlers,
...importListExclusions.actionHandlers,
...languages.actionHandlers,
...metadataProfiles.actionHandlers,
...mediaManagement.actionHandlers,
...metadata.actionHandlers,
@ -137,6 +141,7 @@ export const reducers = createHandleActions({
...indexers.reducers,
...importLists.reducers,
...importListExclusions.reducers,
...languages.reducers,
...metadataProfiles.reducers,
...mediaManagement.reducers,
...metadata.reducers,

View file

@ -0,0 +1,34 @@
import $ from 'jquery';
function getTranslations() {
let localization = null;
const ajaxOptions = {
async: false,
type: 'GET',
global: false,
dataType: 'json',
url: `${window.Lidarr.apiRoot}/localization`,
success: function(data) {
localization = data.Strings;
}
};
ajaxOptions.headers = ajaxOptions.headers || {};
ajaxOptions.headers['X-Api-Key'] = window.Lidarr.apiKey;
$.ajax(ajaxOptions);
return localization;
}
const translations = getTranslations();
export default function translate(key, args = '') {
if (args) {
const translatedKey = translate(key);
return translatedKey.replace(/\{(\d+)\}/g, (match, index) => {
return args[index];
});
}
return translations[key] || key;
}