From 73c5ec1da4dd00301e1b0dddbcea37590a99b045 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 18 Jul 2023 20:20:27 -0700 Subject: [PATCH 1/9] Fixed: Improve translation loading --- frontend/src/App/App.js | 7 +- frontend/src/Components/Page/ErrorPage.js | 8 +- frontend/src/Components/Page/PageConnector.js | 30 ++-- .../Components/Page/Sidebar/PageSidebar.js | 128 +++++++++++++----- .../ManageDownloadClientsEditModalContent.tsx | 22 ++- .../ManageDownloadClientsModalContent.tsx | 24 +++- .../Manage/Tags/TagsModalContent.tsx | 21 ++- .../ManageImportListsEditModalContent.tsx | 22 ++- .../Manage/ManageImportListsModalContent.tsx | 24 +++- .../Manage/Tags/TagsModalContent.tsx | 21 ++- .../Edit/ManageIndexersEditModalContent.tsx | 22 ++- .../Manage/ManageIndexersModalContent.tsx | 28 +++- .../Indexers/Manage/Tags/TagsModalContent.tsx | 21 ++- frontend/src/Store/Actions/appActions.js | 21 ++- frontend/src/Store/Actions/episodeActions.js | 8 +- .../String/{translate.js => translate.ts} | 13 +- frontend/src/bootstrap.tsx | 8 +- 17 files changed, 326 insertions(+), 102 deletions(-) rename frontend/src/Utilities/String/{translate.js => translate.ts} (66%) diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index ea29231c2..781b2ca10 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -7,13 +7,13 @@ import PageConnector from 'Components/Page/PageConnector'; import ApplyTheme from './ApplyTheme'; import AppRoutes from './AppRoutes'; -function App({ store, history, hasTranslationsError }) { +function App({ store, history }) { return ( - + @@ -25,8 +25,7 @@ function App({ store, history, hasTranslationsError }) { App.propTypes = { store: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - hasTranslationsError: PropTypes.bool.isRequired + history: PropTypes.object.isRequired }; export default App; diff --git a/frontend/src/Components/Page/ErrorPage.js b/frontend/src/Components/Page/ErrorPage.js index e7b436d61..214c9dcc9 100644 --- a/frontend/src/Components/Page/ErrorPage.js +++ b/frontend/src/Components/Page/ErrorPage.js @@ -7,7 +7,7 @@ function ErrorPage(props) { const { version, isLocalStorageSupported, - hasTranslationsError, + translationsError, seriesError, customFiltersError, tagsError, @@ -20,8 +20,8 @@ function ErrorPage(props) { if (!isLocalStorageSupported) { errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.'; - } else if (hasTranslationsError) { - errorMessage = 'Failed to load translations from API'; + } else if (translationsError) { + errorMessage = getErrorMessage(translationsError, 'Failed to load translations from API'); } else if (seriesError) { errorMessage = getErrorMessage(seriesError, 'Failed to load series from API'); } else if (customFiltersError) { @@ -52,7 +52,7 @@ function ErrorPage(props) { ErrorPage.propTypes = { version: PropTypes.string.isRequired, isLocalStorageSupported: PropTypes.bool.isRequired, - hasTranslationsError: PropTypes.bool.isRequired, + translationsError: PropTypes.object, seriesError: PropTypes.object, customFiltersError: PropTypes.object, tagsError: PropTypes.object, diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js index 37c7bf8d0..3aa82f31e 100644 --- a/frontend/src/Components/Page/PageConnector.js +++ b/frontend/src/Components/Page/PageConnector.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { createSelector } from 'reselect'; -import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; +import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { fetchCustomFilters } from 'Store/Actions/customFilterActions'; import { fetchSeries } from 'Store/Actions/seriesActions'; import { fetchImportLists, fetchLanguages, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions'; @@ -52,6 +52,7 @@ const selectIsPopulated = createSelector( (state) => state.settings.languages.isPopulated, (state) => state.settings.importLists.isPopulated, (state) => state.system.status.isPopulated, + (state) => state.app.translations.isPopulated, ( seriesIsPopulated, customFiltersIsPopulated, @@ -60,7 +61,8 @@ const selectIsPopulated = createSelector( qualityProfilesIsPopulated, languagesIsPopulated, importListsIsPopulated, - systemStatusIsPopulated + systemStatusIsPopulated, + translationsIsPopulated ) => { return ( seriesIsPopulated && @@ -70,7 +72,8 @@ const selectIsPopulated = createSelector( qualityProfilesIsPopulated && languagesIsPopulated && importListsIsPopulated && - systemStatusIsPopulated + systemStatusIsPopulated && + translationsIsPopulated ); } ); @@ -84,6 +87,7 @@ const selectErrors = createSelector( (state) => state.settings.languages.error, (state) => state.settings.importLists.error, (state) => state.system.status.error, + (state) => state.app.translations.error, ( seriesError, customFiltersError, @@ -92,7 +96,8 @@ const selectErrors = createSelector( qualityProfilesError, languagesError, importListsError, - systemStatusError + systemStatusError, + translationsError ) => { const hasError = !!( seriesError || @@ -102,7 +107,8 @@ const selectErrors = createSelector( qualityProfilesError || languagesError || importListsError || - systemStatusError + systemStatusError || + translationsError ); return { @@ -114,7 +120,8 @@ const selectErrors = createSelector( qualityProfilesError, languagesError, importListsError, - systemStatusError + systemStatusError, + translationsError }; } ); @@ -173,6 +180,9 @@ function createMapDispatchToProps(dispatch, props) { dispatchFetchStatus() { dispatch(fetchStatus()); }, + dispatchFetchTranslations() { + dispatch(fetchTranslations()); + }, onResize(dimensions) { dispatch(saveDimensions(dimensions)); }, @@ -205,6 +215,7 @@ class PageConnector extends Component { this.props.dispatchFetchImportLists(); this.props.dispatchFetchUISettings(); this.props.dispatchFetchStatus(); + this.props.dispatchFetchTranslations(); } } @@ -220,7 +231,6 @@ class PageConnector extends Component { render() { const { - hasTranslationsError, isPopulated, hasError, dispatchFetchSeries, @@ -230,15 +240,15 @@ class PageConnector extends Component { dispatchFetchImportLists, dispatchFetchUISettings, dispatchFetchStatus, + dispatchFetchTranslations, ...otherProps } = this.props; - if (hasTranslationsError || hasError || !this.state.isLocalStorageSupported) { + if (hasError || !this.state.isLocalStorageSupported) { return ( ); } @@ -259,7 +269,6 @@ class PageConnector extends Component { } PageConnector.propTypes = { - hasTranslationsError: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired, hasError: PropTypes.bool.isRequired, isSidebarVisible: PropTypes.bool.isRequired, @@ -271,6 +280,7 @@ PageConnector.propTypes = { dispatchFetchImportLists: PropTypes.func.isRequired, dispatchFetchUISettings: PropTypes.func.isRequired, dispatchFetchStatus: PropTypes.func.isRequired, + dispatchFetchTranslations: PropTypes.func.isRequired, onSidebarVisibleChange: PropTypes.func.isRequired }; diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js index fcf4bc02f..ba07578da 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebar.js +++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js @@ -21,16 +21,22 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth); const links = [ { iconName: icons.SERIES_CONTINUING, - title: translate('Series'), + get title() { + return translate('Series'); + }, to: '/', alias: '/series', children: [ { - title: translate('AddNew'), + get title() { + return translate('AddNew'); + }, to: '/add/new' }, { - title: translate('LibraryImport'), + get title() { + return translate('LibraryImport'); + }, to: '/add/import' } ] @@ -38,26 +44,36 @@ const links = [ { iconName: icons.CALENDAR, - title: translate('Calendar'), + get title() { + return translate('Calendar'); + }, to: '/calendar' }, { iconName: icons.ACTIVITY, - title: translate('Activity'), + get title() { + return translate('Activity'); + }, to: '/activity/queue', children: [ { - title: translate('Queue'), + get title() { + return translate('Queue'); + }, to: '/activity/queue', statusComponent: QueueStatusConnector }, { - title: translate('History'), + get title() { + return translate('History'); + }, to: '/activity/history' }, { - title: translate('Blocklist'), + get title() { + return translate('Blocklist'); + }, to: '/activity/blocklist' } ] @@ -65,15 +81,21 @@ const links = [ { iconName: icons.WARNING, - title: translate('Wanted'), + get title() { + return translate('Wanted'); + }, to: '/wanted/missing', children: [ { - title: translate('Missing'), + get title() { + return translate('Missing'); + }, to: '/wanted/missing' }, { - title: translate('CutoffUnmet'), + get title() { + return translate('CutoffUnmet'); + }, to: '/wanted/cutoffunmet' } ] @@ -81,59 +103,87 @@ const links = [ { iconName: icons.SETTINGS, - title: translate('Settings'), + get title() { + return translate('Settings'); + }, to: '/settings', children: [ { - title: translate('MediaManagement'), + get title() { + return translate('MediaManagement'); + }, to: '/settings/mediamanagement' }, { - title: translate('Profiles'), + get title() { + return translate('Profiles'); + }, to: '/settings/profiles' }, { - title: translate('Quality'), + get title() { + return translate('Quality'); + }, to: '/settings/quality' }, { - title: translate('CustomFormats'), + get title() { + return translate('CustomFormats'); + }, to: '/settings/customformats' }, { - title: translate('Indexers'), + get title() { + return translate('Indexers'); + }, to: '/settings/indexers' }, { - title: translate('DownloadClients'), + get title() { + return translate('DownloadClients'); + }, to: '/settings/downloadclients' }, { - title: translate('ImportLists'), + get title() { + return translate('ImportLists'); + }, to: '/settings/importlists' }, { - title: translate('Connect'), + get title() { + return translate('Connect'); + }, to: '/settings/connect' }, { - title: translate('Metadata'), + get title() { + return translate('Metadata'); + }, to: '/settings/metadata' }, { - title: translate('MetadataSource'), + get title() { + return translate('MetadataSource'); + }, to: '/settings/metadatasource' }, { - title: translate('Tags'), + get title() { + return translate('Tags'); + }, to: '/settings/tags' }, { - title: translate('General'), + get title() { + return translate('General'); + }, to: '/settings/general' }, { - title: translate('UI'), + get title() { + return translate('UI'); + }, to: '/settings/ui' } ] @@ -141,32 +191,46 @@ const links = [ { iconName: icons.SYSTEM, - title: translate('System'), + get title() { + return translate('System'); + }, to: '/system/status', children: [ { - title: translate('Status'), + get title() { + return translate('Status'); + }, to: '/system/status', statusComponent: HealthStatusConnector }, { - title: translate('Tasks'), + get title() { + return translate('Tasks'); + }, to: '/system/tasks' }, { - title: translate('Backup'), + get title() { + return translate('Backup'); + }, to: '/system/backup' }, { - title: translate('Updates'), + get title() { + return translate('Updates'); + }, to: '/system/updates' }, { - title: translate('Events'), + get title() { + return translate('Events'); + }, to: '/system/events' }, { - title: translate('LogFiles'), + get title() { + return translate('LogFiles'); + }, to: '/system/logs/files' } ] diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx index 458ee321e..3a024b559 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx @@ -27,9 +27,25 @@ interface ManageDownloadClientsEditModalContentProps { const NO_CHANGE = 'noChange'; const enableOptions = [ - { key: NO_CHANGE, value: translate('NoChange'), disabled: true }, - { key: 'enabled', value: translate('Enabled') }, - { key: 'disabled', value: translate('Disabled') }, + { + key: NO_CHANGE, + get value() { + return translate('NoChange'); + }, + disabled: true, + }, + { + key: 'enabled', + get value() { + return translate('Enabled'); + }, + }, + { + key: 'disabled', + get value() { + return translate('Disabled'); + }, + }, ]; function ManageDownloadClientsEditModalContent( diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx index 5d9f434c0..21f45cc97 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx @@ -36,37 +36,49 @@ type OnSelectedChangeCallback = React.ComponentProps< const COLUMNS = [ { name: 'name', - label: translate('Name'), + get label() { + return translate('Name'); + }, isSortable: true, isVisible: true, }, { name: 'implementation', - label: translate('Implementation'), + get label() { + return translate('Implementation'); + }, isSortable: true, isVisible: true, }, { name: 'enable', - label: translate('Enabled'), + get label() { + return translate('Enabled'); + }, isSortable: true, isVisible: true, }, { name: 'priority', - label: translate('Priority'), + get label() { + return translate('Priority'); + }, isSortable: true, isVisible: true, }, { name: 'removeCompletedDownloads', - label: translate('RemoveCompleted'), + get label() { + return translate('RemoveCompleted'); + }, isSortable: true, isVisible: true, }, { name: 'removeFailedDownloads', - label: translate('RemoveFailed'), + get label() { + return translate('RemoveFailed'); + }, isSortable: true, isVisible: true, }, diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Tags/TagsModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Tags/TagsModalContent.tsx index 98f33c109..e0899ff39 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Tags/TagsModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Tags/TagsModalContent.tsx @@ -72,9 +72,24 @@ function TagsModalContent(props: TagsModalContentProps) { }, [tags, applyTags, onApplyTagsPress]); const applyTagsOptions = [ - { key: 'add', value: translate('Add') }, - { key: 'remove', value: translate('Remove') }, - { key: 'replace', value: translate('Replace') }, + { + key: 'add', + get value() { + return translate('Add'); + }, + }, + { + key: 'remove', + get value() { + return translate('Remove'); + }, + }, + { + key: 'replace', + get value() { + return translate('Replace'); + }, + }, ]; return ( diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx index e5b28adf3..8660f2fd3 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/Edit/ManageImportListsEditModalContent.tsx @@ -26,9 +26,25 @@ interface ManageImportListsEditModalContentProps { const NO_CHANGE = 'noChange'; const autoAddOptions = [ - { key: NO_CHANGE, value: translate('NoChange'), disabled: true }, - { key: 'enabled', value: translate('Enabled') }, - { key: 'disabled', value: translate('Disabled') }, + { + key: NO_CHANGE, + get value() { + return translate('NoChange'); + }, + disabled: true, + }, + { + key: 'enabled', + get value() { + return translate('Enabled'); + }, + }, + { + key: 'disabled', + get value() { + return translate('Disabled'); + }, + }, ]; function ManageImportListsEditModalContent( diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx index fdcc20f8b..5d78e38d2 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/ManageImportListsModalContent.tsx @@ -36,37 +36,49 @@ type OnSelectedChangeCallback = React.ComponentProps< const COLUMNS = [ { name: 'name', - label: translate('Name'), + get label() { + return translate('Name'); + }, isSortable: true, isVisible: true, }, { name: 'implementation', - label: translate('Implementation'), + get label() { + return translate('Implementation'); + }, isSortable: true, isVisible: true, }, { name: 'qualityProfileId', - label: translate('QualityProfile'), + get label() { + return translate('QualityProfile'); + }, isSortable: true, isVisible: true, }, { name: 'rootFolderPath', - label: translate('RootFolder'), + get label() { + return translate('RootFolder'); + }, isSortable: true, isVisible: true, }, { name: 'enableAutomaticAdd', - label: translate('AutoAdd'), + get label() { + return translate('AutoAdd'); + }, isSortable: true, isVisible: true, }, { name: 'tags', - label: translate('Tags'), + get label() { + return translate('Tags'); + }, isSortable: true, isVisible: true, }, diff --git a/frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.tsx b/frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.tsx index 6072be5ff..9d4af820e 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.tsx +++ b/frontend/src/Settings/ImportLists/ImportLists/Manage/Tags/TagsModalContent.tsx @@ -70,9 +70,24 @@ function TagsModalContent(props: TagsModalContentProps) { }, [tags, applyTags, onApplyTagsPress]); const applyTagsOptions = [ - { key: 'add', value: translate('Add') }, - { key: 'remove', value: translate('Remove') }, - { key: 'replace', value: translate('Replace') }, + { + key: 'add', + get value() { + return translate('Add'); + }, + }, + { + key: 'remove', + get value() { + return translate('Remove'); + }, + }, + { + key: 'replace', + get value() { + return translate('Replace'); + }, + }, ]; return ( diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx index c06207fbb..8af789d93 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx +++ b/frontend/src/Settings/Indexers/Indexers/Manage/Edit/ManageIndexersEditModalContent.tsx @@ -27,9 +27,25 @@ interface ManageIndexersEditModalContentProps { const NO_CHANGE = 'noChange'; const enableOptions = [ - { key: NO_CHANGE, value: translate('NoChange'), disabled: true }, - { key: 'enabled', value: translate('Enabled') }, - { key: 'disabled', value: translate('Disabled') }, + { + key: NO_CHANGE, + get value() { + return translate('NoChange'); + }, + disabled: true, + }, + { + key: 'enabled', + get value() { + return translate('Enabled'); + }, + }, + { + key: 'disabled', + get value() { + return translate('Disabled'); + }, + }, ]; function ManageIndexersEditModalContent( diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx index 20418c682..da24b4412 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx +++ b/frontend/src/Settings/Indexers/Indexers/Manage/ManageIndexersModalContent.tsx @@ -36,43 +36,57 @@ type OnSelectedChangeCallback = React.ComponentProps< const COLUMNS = [ { name: 'name', - label: translate('Name'), + get label() { + return translate('Name'); + }, isSortable: true, isVisible: true, }, { name: 'implementation', - label: translate('Implementation'), + get label() { + return translate('Implementation'); + }, isSortable: true, isVisible: true, }, { name: 'enableRss', - label: translate('EnableRSS'), + get label() { + return translate('EnableRSS'); + }, isSortable: true, isVisible: true, }, { name: 'enableAutomaticSearch', - label: translate('EnableAutomaticSearch'), + get label() { + return translate('EnableAutomaticSearch'); + }, isSortable: true, isVisible: true, }, { name: 'enableInteractiveSearch', - label: translate('EnableInteractiveSearch'), + get label() { + return translate('EnableInteractiveSearch'); + }, isSortable: true, isVisible: true, }, { name: 'priority', - label: translate('Priority'), + get label() { + return translate('Priority'); + }, isSortable: true, isVisible: true, }, { name: 'tags', - label: translate('Tags'), + get label() { + return translate('Tags'); + }, isSortable: true, isVisible: true, }, diff --git a/frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.tsx b/frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.tsx index 32138fb5f..fb1e6b847 100644 --- a/frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.tsx +++ b/frontend/src/Settings/Indexers/Indexers/Manage/Tags/TagsModalContent.tsx @@ -70,9 +70,24 @@ function TagsModalContent(props: TagsModalContentProps) { }, [tags, applyTags, onApplyTagsPress]); const applyTagsOptions = [ - { key: 'add', value: translate('Add') }, - { key: 'remove', value: translate('Remove') }, - { key: 'replace', value: translate('Replace') }, + { + key: 'add', + get value() { + return translate('Add'); + }, + }, + { + key: 'remove', + get value() { + return translate('Remove'); + }, + }, + { + key: 'replace', + get value() { + return translate('Replace'); + }, + }, ]; return ( diff --git a/frontend/src/Store/Actions/appActions.js b/frontend/src/Store/Actions/appActions.js index 464b7b1a0..3564c7fa0 100644 --- a/frontend/src/Store/Actions/appActions.js +++ b/frontend/src/Store/Actions/appActions.js @@ -4,6 +4,7 @@ import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import getSectionState from 'Utilities/State/getSectionState'; import updateSectionState from 'Utilities/State/updateSectionState'; +import { fetchTranslations as fetchAppTranslations } from 'Utilities/String/translate'; import createHandleActions from './Creators/createHandleActions'; function getDimensions(width, height) { @@ -41,7 +42,12 @@ export const defaultState = { isReconnecting: false, isDisconnected: false, isRestarting: false, - isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen + isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen, + translations: { + isFetching: true, + isPopulated: false, + error: null + } }; // @@ -53,6 +59,7 @@ export const SAVE_DIMENSIONS = 'app/saveDimensions'; export const SET_VERSION = 'app/setVersion'; export const SET_APP_VALUE = 'app/setAppValue'; export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible'; +export const FETCH_TRANSLATIONS = 'app/fetchTranslations'; export const PING_SERVER = 'app/pingServer'; @@ -66,6 +73,7 @@ export const setAppValue = createAction(SET_APP_VALUE); export const showMessage = createAction(SHOW_MESSAGE); export const hideMessage = createAction(HIDE_MESSAGE); export const pingServer = createThunk(PING_SERVER); +export const fetchTranslations = createThunk(FETCH_TRANSLATIONS); // // Helpers @@ -127,6 +135,17 @@ function pingServerAfterTimeout(getState, dispatch) { export const actionHandlers = handleThunks({ [PING_SERVER]: function(getState, payload, dispatch) { pingServerAfterTimeout(getState, dispatch); + }, + [FETCH_TRANSLATIONS]: async function(getState, payload, dispatch) { + const isFetchingComplete = await fetchAppTranslations(); + + dispatch(setAppValue({ + translations: { + isFetching: false, + isPopulated: isFetchingComplete, + error: isFetchingComplete ? null : 'Failed to load translations from API' + } + })); } }); diff --git a/frontend/src/Store/Actions/episodeActions.js b/frontend/src/Store/Actions/episodeActions.js index b1860c089..c7f05aa8b 100644 --- a/frontend/src/Store/Actions/episodeActions.js +++ b/frontend/src/Store/Actions/episodeActions.js @@ -114,10 +114,14 @@ export const defaultState = { }, { name: 'customFormatScore', - columnLabel: translate('CustomFormatScore'), + get columnLabel() { + return translate('CustomFormatScore'); + }, label: React.createElement(Icon, { name: icons.SCORE, - title: translate('CustomFormatScore') + get title() { + return translate('CustomFormatScore'); + } }), isVisible: false }, diff --git a/frontend/src/Utilities/String/translate.js b/frontend/src/Utilities/String/translate.ts similarity index 66% rename from frontend/src/Utilities/String/translate.js rename to frontend/src/Utilities/String/translate.ts index c2dd96291..9197e5137 100644 --- a/frontend/src/Utilities/String/translate.js +++ b/frontend/src/Utilities/String/translate.ts @@ -4,14 +4,14 @@ function getTranslations() { return createAjaxRequest({ global: false, dataType: 'json', - url: '/localization' + url: '/localization', }).request; } -let translations = {}; +let translations: Record = {}; -export function fetchTranslations() { - return new Promise(async(resolve) => { +export async function fetchTranslations(): Promise { + return new Promise(async (resolve) => { try { const data = await getTranslations(); translations = data.strings; @@ -23,7 +23,10 @@ export function fetchTranslations() { }); } -export default function translate(key, tokens) { +export default function translate( + key: string, + tokens?: Record +) { const translation = translations[key] || key; if (tokens) { diff --git a/frontend/src/bootstrap.tsx b/frontend/src/bootstrap.tsx index a729cb3c5..6a6d7fc67 100644 --- a/frontend/src/bootstrap.tsx +++ b/frontend/src/bootstrap.tsx @@ -2,7 +2,6 @@ import { createBrowserHistory } from 'history'; import React from 'react'; import { render } from 'react-dom'; import createAppStore from 'Store/createAppStore'; -import { fetchTranslations } from 'Utilities/String/translate'; import App from './App/App'; import 'Diag/ConsoleApi'; @@ -10,14 +9,9 @@ import 'Diag/ConsoleApi'; export async function bootstrap() { const history = createBrowserHistory(); const store = createAppStore(history); - const hasTranslationsError = !(await fetchTranslations()); render( - , + , document.getElementById('root') ); } From 622f820da913eaadf3376a10556a2af7e85babc7 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 19 Jul 2023 23:25:13 +0300 Subject: [PATCH 2/9] Run API docs workflow on changes to Sonarr.Http --- .github/workflows/api_docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/api_docs.yml b/.github/workflows/api_docs.yml index 4838b5e0b..c1ea11cc0 100644 --- a/.github/workflows/api_docs.yml +++ b/.github/workflows/api_docs.yml @@ -11,6 +11,7 @@ on: - ".github/workflows/api_docs.yml" - "docs.sh" - "src/Sonarr.Api.*/**" + - "src/Sonarr.Http/**" - "src/**/*.csproj" - "src/*" From 6d53d2a153a98070c42d0619c15902b6bd5dfab4 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 19 Jul 2023 17:52:19 -0700 Subject: [PATCH 3/9] Fixed: Translations for columns --- frontend/src/Components/Icon.js | 4 ++-- frontend/src/Store/Actions/episodeActions.js | 5 ++--- .../src/Store/Middleware/createPersistState.js | 15 +++++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/frontend/src/Components/Icon.js b/frontend/src/Components/Icon.js index 8c534d8d4..90842b039 100644 --- a/frontend/src/Components/Icon.js +++ b/frontend/src/Components/Icon.js @@ -43,7 +43,7 @@ class Icon extends PureComponent { return ( {icon} @@ -60,7 +60,7 @@ Icon.propTypes = { name: PropTypes.object.isRequired, kind: PropTypes.string.isRequired, size: PropTypes.number.isRequired, - title: PropTypes.string, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), darken: PropTypes.bool.isRequired, isSpinning: PropTypes.bool.isRequired, fixedWidth: PropTypes.bool.isRequired diff --git a/frontend/src/Store/Actions/episodeActions.js b/frontend/src/Store/Actions/episodeActions.js index c7f05aa8b..b84dfe168 100644 --- a/frontend/src/Store/Actions/episodeActions.js +++ b/frontend/src/Store/Actions/episodeActions.js @@ -119,9 +119,8 @@ export const defaultState = { }, label: React.createElement(Icon, { name: icons.SCORE, - get title() { - return translate('CustomFormatScore'); - } + title: () => translate('CustomFormatScore') + }), isVisible: false }, diff --git a/frontend/src/Store/Middleware/createPersistState.js b/frontend/src/Store/Middleware/createPersistState.js index fc6b5c88c..aa16ffa9e 100644 --- a/frontend/src/Store/Middleware/createPersistState.js +++ b/frontend/src/Store/Middleware/createPersistState.js @@ -36,10 +36,17 @@ function mergeColumns(path, initialState, persistedState, computedState) { const column = initialColumns.find((i) => i.name === persistedColumn.name); if (column) { - columns.push({ - ...column, - isVisible: persistedColumn.isVisible - }); + const newColumn = {}; + + // We can't use a spread operator or Object.assign to clone the column + // or any accessors are lost and can break translations. + for (const prop of Object.keys(column)) { + Object.defineProperty(newColumn, prop, Object.getOwnPropertyDescriptor(column, prop)); + } + + newColumn.isVisible = persistedColumn.isVisible; + + columns.push(newColumn); } }); From 360d989cb047d0f752dd71b806aa0a746e3b5f3d Mon Sep 17 00:00:00 2001 From: bakerboy448 <55419169+bakerboy448@users.noreply.github.com> Date: Wed, 19 Jul 2023 20:11:05 -0500 Subject: [PATCH 4/9] New: Log when testing for matching Remote Path Mapping --- .../RemotePathMappings/RemotePathMappingService.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs index 859bcbee4..5e033b582 100644 --- a/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs +++ b/src/NzbDrone.Core/RemotePathMappings/RemotePathMappingService.cs @@ -101,12 +101,12 @@ namespace NzbDrone.Core.RemotePathMappings if (remotePath.IsEmpty) { - throw new ArgumentException("Invalid RemotePath"); + throw new ArgumentException("Invalid RemotePath. RemotePath cannot be empty."); } if (localPath.IsEmpty || !localPath.IsRooted) { - throw new ArgumentException("Invalid LocalPath"); + throw new ArgumentException("Invalid LocalPath. LocalPath cannot be empty and must not be the root."); } if (!_diskProvider.FolderExists(localPath.FullPath)) @@ -116,7 +116,7 @@ namespace NzbDrone.Core.RemotePathMappings if (existing.Exists(r => r.Host == mapping.Host && r.RemotePath == mapping.RemotePath)) { - throw new InvalidOperationException("RemotePath already mounted."); + throw new InvalidOperationException("RemotePath already configured."); } } @@ -127,11 +127,14 @@ namespace NzbDrone.Core.RemotePathMappings return remotePath; } + _logger.Trace("Evaluating remote path remote mappings for match to host [{0}] and remote path [{1}]", host, remotePath.FullPath); foreach (var mapping in All()) { + _logger.Trace("Checking configured remote path mapping: {0} - {1}", mapping.Host, mapping.RemotePath); if (host.Equals(mapping.Host, StringComparison.InvariantCultureIgnoreCase) && new OsPath(mapping.RemotePath).Contains(remotePath)) { var localPath = new OsPath(mapping.LocalPath) + (remotePath - new OsPath(mapping.RemotePath)); + _logger.Debug("Remapped remote path [{0}] to local path [{1}] for host [{2}]", remotePath, localPath, host); return localPath; } @@ -147,11 +150,14 @@ namespace NzbDrone.Core.RemotePathMappings return localPath; } + _logger.Trace("Evaluating remote path local mappings for match to host [{0}] and local path [{1}]", host, localPath.FullPath); foreach (var mapping in All()) { + _logger.Trace("Checking configured remote path mapping {0} - {1}", mapping.Host, mapping.RemotePath); if (host.Equals(mapping.Host, StringComparison.InvariantCultureIgnoreCase) && new OsPath(mapping.LocalPath).Contains(localPath)) { var remotePath = new OsPath(mapping.RemotePath) + (localPath - new OsPath(mapping.LocalPath)); + _logger.Debug("Remapped local path [{0}] to remote path [{1}] for host [{2}]", localPath, remotePath, host); return remotePath; } From 93e8ff0ac7610fa8739f2e577ece98c2c06c8881 Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Thu, 20 Jul 2023 03:19:43 +0200 Subject: [PATCH 5/9] Translate System pages --- frontend/src/System/Backup/BackupRow.js | 18 +-- frontend/src/System/Backup/Backups.js | 23 ++-- .../Backup/RestoreBackupModalContent.js | 25 ++-- frontend/src/System/Events/LogsTable.js | 11 +- .../System/Events/LogsTableDetailsModal.js | 13 ++- frontend/src/System/Logs/Files/LogFiles.js | 25 ++-- .../System/Logs/Files/LogFilesConnector.js | 3 +- .../src/System/Logs/Files/LogFilesTableRow.js | 3 +- frontend/src/System/Logs/LogsNavMenu.js | 5 +- frontend/src/System/Status/About/About.js | 28 +++-- .../src/System/Status/DiskSpace/DiskSpace.js | 15 ++- frontend/src/System/Status/Health/Health.js | 27 +++-- .../src/System/Status/MoreInfo/MoreInfo.js | 39 +++++-- frontend/src/System/Status/Status.js | 3 +- .../src/System/Tasks/Queued/QueuedTaskRow.js | 15 +-- .../src/System/Tasks/Queued/QueuedTasks.js | 23 +++- .../System/Tasks/Scheduled/ScheduledTasks.js | 23 +++- frontend/src/System/Tasks/Tasks.js | 3 +- frontend/src/System/Updates/Updates.js | 33 +++--- src/NzbDrone.Core/Localization/Core/en.json | 110 ++++++++++++++++-- 20 files changed, 319 insertions(+), 126 deletions(-) diff --git a/frontend/src/System/Backup/BackupRow.js b/frontend/src/System/Backup/BackupRow.js index ca36b98dc..ad63544e3 100644 --- a/frontend/src/System/Backup/BackupRow.js +++ b/frontend/src/System/Backup/BackupRow.js @@ -9,6 +9,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; import { icons, kinds } from 'Helpers/Props'; import formatBytes from 'Utilities/Number/formatBytes'; +import translate from 'Utilities/String/translate'; import RestoreBackupModalConnector from './RestoreBackupModalConnector'; import styles from './BackupRow.css'; @@ -75,14 +76,14 @@ class BackupRow extends Component { } = this.state; let iconClassName = icons.SCHEDULED; - let iconTooltip = 'Scheduled'; + let iconTooltip = translate('Scheduled'); if (type === 'manual') { iconClassName = icons.INTERACTIVE; - iconTooltip = 'Manual'; + iconTooltip = translate('Manual'); } else if (type === 'update') { iconClassName = icons.UPDATE; - iconTooltip = 'Before update'; + iconTooltip = translate('BeforeUpdate'); } return ( @@ -115,12 +116,13 @@ class BackupRow extends Component { @@ -136,9 +138,11 @@ class BackupRow extends Component { diff --git a/frontend/src/System/Backup/Backups.js b/frontend/src/System/Backup/Backups.js index c4adfbf50..fa78dd769 100644 --- a/frontend/src/System/Backup/Backups.js +++ b/frontend/src/System/Backup/Backups.js @@ -10,6 +10,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import BackupRow from './BackupRow'; import RestoreBackupModalConnector from './RestoreBackupModalConnector'; @@ -20,17 +21,23 @@ const columns = [ }, { name: 'name', - label: 'Name', + get label() { + return translate('Name'); + }, isVisible: true }, { name: 'size', - label: 'Size', + get label() { + return translate('Size'); + }, isVisible: true }, { name: 'time', - label: 'Time', + get label() { + return translate('Time'); + }, isVisible: true }, { @@ -81,18 +88,18 @@ class Backups extends Component { const noBackups = isPopulated && !items.length; return ( - + @@ -108,14 +115,14 @@ class Backups extends Component { { !isFetching && !!error && - Unable to load backups + {translate('UnableToLoadBackups')} } { noBackups && - No backups are available + {translate('NoBackupsAreAvailable')} } diff --git a/frontend/src/System/Backup/RestoreBackupModalContent.js b/frontend/src/System/Backup/RestoreBackupModalContent.js index 71bd5a3b9..9b5daa9f4 100644 --- a/frontend/src/System/Backup/RestoreBackupModalContent.js +++ b/frontend/src/System/Backup/RestoreBackupModalContent.js @@ -9,11 +9,12 @@ import ModalContent from 'Components/Modal/ModalContent'; import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './RestoreBackupModalContent.css'; function getErrorMessage(error) { if (!error || !error.responseJSON || !error.responseJSON.message) { - return 'Error restoring backup'; + return translate('ErrorRestoringBackup'); } return error.responseJSON.message; @@ -145,7 +146,9 @@ class RestoreBackupModalContent extends Component { { - !!id && `Would you like to restore the backup '${name}'?` + !!id && translate('WouldYouLikeToRestoreBackup', { + name + }) } { @@ -167,7 +170,9 @@ class RestoreBackupModalContent extends Component { /> -
Restore
+
+ {translate('Restore')} +
@@ -178,7 +183,9 @@ class RestoreBackupModalContent extends Component { />
-
Restart
+
+ {translate('Restart')} +
@@ -189,18 +196,20 @@ class RestoreBackupModalContent extends Component { />
-
Reload
+
+ {translate('Reload')} +
- Note: Sonarr will automatically restart and reload the UI during the restore process. + {translate('RestartReloadNote')}
- Restore + {translate('Restore')}
diff --git a/frontend/src/System/Events/LogsTable.js b/frontend/src/System/Events/LogsTable.js index 5d4bc8513..1c37a03ba 100644 --- a/frontend/src/System/Events/LogsTable.js +++ b/frontend/src/System/Events/LogsTable.js @@ -13,6 +13,7 @@ import TableBody from 'Components/Table/TableBody'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TablePager from 'Components/Table/TablePager'; import { align, icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import LogsTableRow from './LogsTableRow'; function LogsTable(props) { @@ -33,11 +34,11 @@ function LogsTable(props) { } = props; return ( - + @@ -83,7 +84,7 @@ function LogsTable(props) { { isPopulated && !error && !items.length && - No events found + {translate('NoEventsFound')} } diff --git a/frontend/src/System/Events/LogsTableDetailsModal.js b/frontend/src/System/Events/LogsTableDetailsModal.js index c3681220d..13329f17b 100644 --- a/frontend/src/System/Events/LogsTableDetailsModal.js +++ b/frontend/src/System/Events/LogsTableDetailsModal.js @@ -8,6 +8,7 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import Scroller from 'Components/Scroller/Scroller'; import { scrollDirections } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; import styles from './LogsTableDetailsModal.css'; function LogsTableDetailsModal(props) { @@ -27,11 +28,13 @@ function LogsTableDetailsModal(props) { onModalClose={onModalClose} > - Details + {translate('Details')} -
Message
+
+ {translate('Message')} +
-
Exception
+
+ {translate('Exception')} +
diff --git a/frontend/src/System/Logs/Files/LogFiles.js b/frontend/src/System/Logs/Files/LogFiles.js index 3d7f708aa..23b3bb40e 100644 --- a/frontend/src/System/Logs/Files/LogFiles.js +++ b/frontend/src/System/Logs/Files/LogFiles.js @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Alert from 'Components/Alert'; -import Link from 'Components/Link/Link'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; @@ -12,18 +11,24 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import { icons, kinds } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; +import InlineMarkdown from '../../../Components/Markdown/InlineMarkdown'; import LogsNavMenu from '../LogsNavMenu'; import LogFilesTableRow from './LogFilesTableRow'; const columns = [ { name: 'filename', - label: 'Filename', + get label() { + return translate('Filename'); + }, isVisible: true }, { name: 'lastWriteTime', - label: 'Last Write Time', + get label() { + return translate('LastWriteTime'); + }, isVisible: true }, { @@ -50,7 +55,7 @@ class LogFiles extends Component { } = this.props; return ( - + @@ -58,7 +63,7 @@ class LogFiles extends Component {
- Log files are located in: {location} + {translate('LogFilesLocation', { + location + })}
{ currentLogView === 'Log Files' &&
- The log level defaults to 'Info' and can be changed in General Settings +
}
@@ -118,7 +125,7 @@ class LogFiles extends Component { { !isFetching && !items.length && - No log files + {translate('NoLogFiles')} } diff --git a/frontend/src/System/Logs/Files/LogFilesConnector.js b/frontend/src/System/Logs/Files/LogFilesConnector.js index 98a55f32f..75921f346 100644 --- a/frontend/src/System/Logs/Files/LogFilesConnector.js +++ b/frontend/src/System/Logs/Files/LogFilesConnector.js @@ -7,6 +7,7 @@ import { executeCommand } from 'Store/Actions/commandActions'; import { fetchLogFiles } from 'Store/Actions/systemActions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import combinePath from 'Utilities/String/combinePath'; +import translate from 'Utilities/String/translate'; import LogFiles from './LogFiles'; function createMapStateToProps() { @@ -29,7 +30,7 @@ function createMapStateToProps() { isFetching, items, deleteFilesExecuting, - currentLogView: 'Log Files', + currentLogView: translate('LogFiles'), location: combinePath(isWindows, appData, ['logs']) }; } diff --git a/frontend/src/System/Logs/Files/LogFilesTableRow.js b/frontend/src/System/Logs/Files/LogFilesTableRow.js index ef08ada4e..ba0339b84 100644 --- a/frontend/src/System/Logs/Files/LogFilesTableRow.js +++ b/frontend/src/System/Logs/Files/LogFilesTableRow.js @@ -4,6 +4,7 @@ import Link from 'Components/Link/Link'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRow from 'Components/Table/TableRow'; +import translate from 'Utilities/String/translate'; import styles from './LogFilesTableRow.css'; class LogFilesTableRow extends Component { @@ -32,7 +33,7 @@ class LogFilesTableRow extends Component { target="_blank" noRouter={true} > - Download + {translate('Download')}
diff --git a/frontend/src/System/Logs/LogsNavMenu.js b/frontend/src/System/Logs/LogsNavMenu.js index cc485f270..923e4f41c 100644 --- a/frontend/src/System/Logs/LogsNavMenu.js +++ b/frontend/src/System/Logs/LogsNavMenu.js @@ -4,6 +4,7 @@ import Menu from 'Components/Menu/Menu'; import MenuButton from 'Components/Menu/MenuButton'; import MenuContent from 'Components/Menu/MenuContent'; import MenuItem from 'Components/Menu/MenuItem'; +import translate from 'Utilities/String/translate'; class LogsNavMenu extends Component { @@ -50,13 +51,13 @@ class LogsNavMenu extends Component { - Log Files + {translate('LogFiles')} - Updater Log Files + {translate('UpdaterLogFiles')} diff --git a/frontend/src/System/Status/About/About.js b/frontend/src/System/Status/About/About.js index f212ef947..b0b289135 100644 --- a/frontend/src/System/Status/About/About.js +++ b/frontend/src/System/Status/About/About.js @@ -5,6 +5,7 @@ import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem' import FieldSet from 'Components/FieldSet'; import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; import StartTime from './StartTime'; import styles from './About.css'; @@ -30,25 +31,32 @@ class About extends Component { } = this.props; return ( -
+
{ packageVersion && {packageVersion} {' by '} : packageVersion)} + title={translate('PackageVersion')} + data={(packageAuthor ? + : + packageVersion + )} /> } { isNetCore && } @@ -56,28 +64,28 @@ class About extends Component { { isDocker && } +
{ isFetching && diff --git a/frontend/src/System/Status/Health/Health.js b/frontend/src/System/Status/Health/Health.js index ee0689fe4..8c021ed66 100644 --- a/frontend/src/System/Status/Health/Health.js +++ b/frontend/src/System/Status/Health/Health.js @@ -11,6 +11,7 @@ import TableBody from 'Components/Table/TableBody'; import TableRow from 'Components/Table/TableRow'; import { icons, kinds } from 'Helpers/Props'; import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; import styles from './Health.css'; function getInternalLink(source) { @@ -23,7 +24,7 @@ function getInternalLink(source) { return ( ); @@ -33,7 +34,7 @@ function getInternalLink(source) { return ( ); @@ -41,7 +42,7 @@ function getInternalLink(source) { return ( ); @@ -49,7 +50,7 @@ function getInternalLink(source) { return ( ); @@ -64,7 +65,7 @@ function getTestLink(source, props) { return ( @@ -74,7 +75,7 @@ function getTestLink(source, props) { return ( @@ -93,12 +94,16 @@ const columns = [ }, { name: 'message', - label: 'Message', + get label() { + return translate('Message'); + }, isVisible: true }, { name: 'actions', - label: 'Actions', + get label() { + return translate('Actions'); + }, isVisible: true } ]; @@ -121,7 +126,7 @@ class Health extends Component {
- Health + {translate('Health')} { isFetching && isPopulated && @@ -141,7 +146,7 @@ class Health extends Component { { !healthIssues &&
- No issues with your configuration + {translate('NoIssuesWithYourConfiguration')}
} @@ -186,7 +191,7 @@ class Health extends Component { { diff --git a/frontend/src/System/Status/MoreInfo/MoreInfo.js b/frontend/src/System/Status/MoreInfo/MoreInfo.js index 490230413..53618d882 100644 --- a/frontend/src/System/Status/MoreInfo/MoreInfo.js +++ b/frontend/src/System/Status/MoreInfo/MoreInfo.js @@ -4,6 +4,7 @@ import DescriptionListItemDescription from 'Components/DescriptionList/Descripti import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle'; import FieldSet from 'Components/FieldSet'; import Link from 'Components/Link/Link'; +import translate from 'Utilities/String/translate'; class MoreInfo extends Component { @@ -12,34 +13,46 @@ class MoreInfo extends Component { render() { return ( -
+
- Home page + + {translate('HomePage')} + sonarr.tv - Wiki + + {translate('Wiki')} + wiki.servarr.com/sonarr - Forums + + {translate('Forums')} + forums.sonarr.tv - Twitter + + {translate('Twitter')} + @sonarrtv - Discord + + {translate('Discord')} + discord.sonarr.tv - IRC + + {translate('IRC')} + #sonarr on Libera @@ -47,17 +60,23 @@ class MoreInfo extends Component { Libera webchat - Donations + + {translate('Donations')} + sonarr.tv/donate - Source + + {translate('Source')} + github.com/Sonarr/Sonarr - Feature Requests + + {translate('FeatureRequests')} + forums.sonarr.tv diff --git a/frontend/src/System/Status/Status.js b/frontend/src/System/Status/Status.js index a325495e5..429a149ee 100644 --- a/frontend/src/System/Status/Status.js +++ b/frontend/src/System/Status/Status.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; +import translate from 'Utilities/String/translate'; import AboutConnector from './About/AboutConnector'; import DiskSpaceConnector from './DiskSpace/DiskSpaceConnector'; import HealthConnector from './Health/HealthConnector'; @@ -13,7 +14,7 @@ class Status extends Component { render() { return ( - + diff --git a/frontend/src/System/Tasks/Queued/QueuedTaskRow.js b/frontend/src/System/Tasks/Queued/QueuedTaskRow.js index 31015501d..8b8a62d3a 100644 --- a/frontend/src/System/Tasks/Queued/QueuedTaskRow.js +++ b/frontend/src/System/Tasks/Queued/QueuedTaskRow.js @@ -11,6 +11,7 @@ import formatDate from 'Utilities/Date/formatDate'; import formatDateTime from 'Utilities/Date/formatDateTime'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import titleCase from 'Utilities/String/titleCase'; +import translate from 'Utilities/String/translate'; import styles from './QueuedTaskRow.css'; function getStatusIconProps(status, message) { @@ -198,8 +199,8 @@ class QueuedTaskRow extends Component { { clientUserAgent ? - - from: {clientUserAgent} + + {translate('From')}: {clientUserAgent} : null } @@ -236,7 +237,7 @@ class QueuedTaskRow extends Component { { status === 'queued' && @@ -246,10 +247,10 @@ class QueuedTaskRow extends Component { diff --git a/frontend/src/System/Tasks/Queued/QueuedTasks.js b/frontend/src/System/Tasks/Queued/QueuedTasks.js index e856df532..30301702c 100644 --- a/frontend/src/System/Tasks/Queued/QueuedTasks.js +++ b/frontend/src/System/Tasks/Queued/QueuedTasks.js @@ -4,6 +4,7 @@ import FieldSet from 'Components/FieldSet'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; +import translate from 'Utilities/String/translate'; import QueuedTaskRowConnector from './QueuedTaskRowConnector'; const columns = [ @@ -14,27 +15,37 @@ const columns = [ }, { name: 'commandName', - label: 'Name', + get label() { + return translate('Name'); + }, isVisible: true }, { name: 'queued', - label: 'Queued', + get label() { + return translate('Queued'); + }, isVisible: true }, { name: 'started', - label: 'Started', + get label() { + return translate('Started'); + }, isVisible: true }, { name: 'ended', - label: 'Ended', + get label() { + return translate('Ended'); + }, isVisible: true }, { name: 'duration', - label: 'Duration', + get label() { + return translate('Duration'); + }, isVisible: true }, { @@ -51,7 +62,7 @@ function QueuedTasks(props) { } = props; return ( -
+
{ isFetching && !isPopulated && diff --git a/frontend/src/System/Tasks/Scheduled/ScheduledTasks.js b/frontend/src/System/Tasks/Scheduled/ScheduledTasks.js index 1176db157..c1b17122d 100644 --- a/frontend/src/System/Tasks/Scheduled/ScheduledTasks.js +++ b/frontend/src/System/Tasks/Scheduled/ScheduledTasks.js @@ -4,32 +4,43 @@ import FieldSet from 'Components/FieldSet'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; +import translate from 'Utilities/String/translate'; import ScheduledTaskRowConnector from './ScheduledTaskRowConnector'; const columns = [ { name: 'name', - label: 'Name', + get label() { + return translate('Name'); + }, isVisible: true }, { name: 'interval', - label: 'Interval', + get label() { + return translate('Interval'); + }, isVisible: true }, { name: 'lastExecution', - label: 'Last Execution', + get label() { + return translate('LastExecution'); + }, isVisible: true }, { name: 'lastDuration', - label: 'Last Duration', + get label() { + return translate('LastDuration'); + }, isVisible: true }, { name: 'nextExecution', - label: 'Next Execution', + get label() { + return translate('NextExecution'); + }, isVisible: true }, { @@ -46,7 +57,7 @@ function ScheduledTasks(props) { } = props; return ( -
+
{ isFetching && !isPopulated && diff --git a/frontend/src/System/Tasks/Tasks.js b/frontend/src/System/Tasks/Tasks.js index e08792145..032dbede8 100644 --- a/frontend/src/System/Tasks/Tasks.js +++ b/frontend/src/System/Tasks/Tasks.js @@ -1,12 +1,13 @@ import React from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; +import translate from 'Utilities/String/translate'; import QueuedTasksConnector from './Queued/QueuedTasksConnector'; import ScheduledTasksConnector from './Scheduled/ScheduledTasksConnector'; function Tasks() { return ( - + diff --git a/frontend/src/System/Updates/Updates.js b/frontend/src/System/Updates/Updates.js index cb032dd7f..bded2676d 100644 --- a/frontend/src/System/Updates/Updates.js +++ b/frontend/src/System/Updates/Updates.js @@ -12,6 +12,7 @@ import PageContentBody from 'Components/Page/PageContentBody'; import { icons, kinds } from 'Helpers/Props'; import formatDate from 'Utilities/Date/formatDate'; import formatDateTime from 'Utilities/Date/formatDateTime'; +import translate from 'Utilities/String/translate'; import UpdateChanges from './UpdateChanges'; import styles from './Updates.css'; @@ -43,15 +44,15 @@ class Updates extends Component { const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; - const externalUpdaterPrefix = 'Unable to update Sonarr directly,'; + const externalUpdaterPrefix = translate('UnableToUpdateSonarrDirectly'); const externalUpdaterMessages = { - external: 'Sonarr is configured to use an external update mechanism', - apt: 'use apt to install the update', - docker: 'update the docker container to receive the update' + external: translate('ExternalUpdater'), + apt: translate('AptUpdater'), + docker: translate('DockerUpdater') }; return ( - + { !isPopulated && !hasError && @@ -61,7 +62,7 @@ class Updates extends Component { { noUpdates && - No updates are available + {translate('NoUpdatesAreAvailable')} } @@ -76,7 +77,7 @@ class Updates extends Component { isSpinning={isInstallingUpdate} onPress={onInstallLatestPress} > - Install Latest + {translate('InstallLatest')} : @@ -112,7 +113,7 @@ class Updates extends Component { />
- The latest version of Sonarr is already installed + {translate('OnLatestVersion')}
{ @@ -164,7 +165,7 @@ class Updates extends Component { kind={kinds.SUCCESS} title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} > - Currently Installed + {translate('CurrentlyInstalled')} : null } @@ -176,7 +177,7 @@ class Updates extends Component { kind={kinds.INVERSE} title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} > - Previously Installed + {translate('PreviouslyInstalled')} : null } @@ -184,19 +185,21 @@ class Updates extends Component { { !hasChanges && -
Maintenance Release: See GitHub commit history for details.
+
+ {translate('MaintenanceRelease')} +
} { hasChanges &&
@@ -211,14 +214,14 @@ class Updates extends Component { { !!updatesError &&
- Failed to fetch updates + {translate('FailedToFetchUpdates')}
} { !!generalSettingsError &&
- Failed to update settings + {translate('FailedToUpdateSettings')}
}
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index ea48be0ff..1a3eea420 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1,13 +1,16 @@ { + "About": "About", "AbsoluteEpisodeNumbers": "Absolute Episode Number(s)", + "Actions": "Actions", "Activity": "Activity", "Add": "Add", - "AddNew": "Add New", "Added": "Added", "AddingTag": "Adding tag", + "AddNew": "Add New", "AirDate": "Air Date", "AllTitles": "All Titles", "ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file", + "AppDataDirectory": "AppData directory", "AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update", "Apply": "Apply", "ApplyChanges": "Apply Changes", @@ -20,9 +23,13 @@ "ApplyTagsHelpTextHowToApplySeries": "How to apply tags to the selected series", "ApplyTagsHelpTextRemove": "Remove: Remove the entered tags", "ApplyTagsHelpTextReplace": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)", + "AptUpdater": "Use apt to install the update", "AutoAdd": "Auto Add", "AutomaticAdd": "Automatic Add", "Backup": "Backup", + "BackupNow": "Backup Now", + "Backups": "Backups", + "BeforeUpdate": "Before update", "Blocklist": "Blocklist", "BlocklistRelease": "Blocklist Release", "BlocklistReleaseHelpText": "Prevents Sonarr from automatically grabbing this release again", @@ -30,6 +37,8 @@ "Browser Reload Required": "Browser Reload Required", "Calendar": "Calendar", "Cancel": "Cancel", + "CancelPendingTask": "Are you sure you want to cancel this pending task?", + "Clear": "Clear", "CloneCondition": "Clone Condition", "CloneCustomFormat": "Clone Custom Format", "Close": "Close", @@ -38,11 +47,14 @@ "CountImportListsSelected": "{count} import list(s) selected", "CountIndexersSelected": "{count} indexer(s) selected", "CountSeasons": "{count} seasons", - "CustomFormatScore": "Custom Format Score", + "CurrentlyInstalled": "Currently Installed", "CustomFormats": "Custom Formats", + "CustomFormatScore": "Custom Format Score", "CutoffUnmet": "Cutoff Unmet", "Daily": "Daily", "Delete": "Delete", + "DeleteBackup": "Delete Backup", + "DeleteBackupMessageText": "Are you sure you want to delete the backup '{name}'?", "DeleteCondition": "Delete Condition", "DeleteConditionMessageText": "Are you sure you want to delete the condition '{0}'?", "DeleteCustomFormat": "Delete Custom Format", @@ -55,39 +67,61 @@ "DeleteSelectedIndexersMessageText": "Are you sure you want to delete {count} selected indexer(s)?", "Details": "Details", "Disabled": "Disabled", + "Discord": "Discord", + "DiskSpace": "Disk Space", + "Docker": "Docker", + "DockerUpdater": "Update the docker container to receive the update", + "Donations": "Donations", + "DotNetVersion": ".NET", + "Download": "Download", "DownloadClientCheckNoneAvailableHealthCheckMessage": "No download client is available", "DownloadClientCheckUnableToCommunicateWithHealthCheckMessage": "Unable to communicate with {0}.", "DownloadClientRootFolderHealthCheckMessage": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", + "DownloadClients": "Download Clients", "DownloadClientSortingHealthCheckMessage": "Download client {0} has {1} sorting enabled for Sonarr's category. You should disable sorting in your download client to avoid import issues.", "DownloadClientStatusAllClientHealthCheckMessage": "All download clients are unavailable due to failures", "DownloadClientStatusSingleClientHealthCheckMessage": "Download clients unavailable due to failures: {0}", - "DownloadClients": "Download Clients", + "Duration": "Duration", "Edit": "Edit", "EditSelectedDownloadClients": "Edit Selected Download Clients", "EditSelectedImportLists": "Edit Selected Import Lists", "EditSelectedIndexers": "Edit Selected Indexers", "EditSeries": "Edit Series", "EnableAutomaticSearch": "Enable Automatic Search", + "Enabled": "Enabled", "EnableInteractiveSearch": "Enable Interactive Search", "EnableRSS": "Enable RSS", - "Enabled": "Enabled", "Ended": "Ended", "EpisodeInfo": "Episode Info", "EpisodeNumbers": "Episode Number(s)", + "ErrorRestoringBackup": "Error restoring backup", "Events": "Events", + "Exception": "Exception", "ExistingTag": "Existing tag", "ExportCustomFormat": "Export Custom Format", + "ExternalUpdater": "Sonarr is configured to use an external update mechanism", + "FailedToFetchUpdates": "Failed to fetch updates", + "FailedToUpdateSettings": "Failed to update settings", + "FeatureRequests": "Feature Requests", + "Filename": "Filename", + "Fixed": "Fixed", + "Forums": "Forums", + "FreeSpace": "Free Space", + "From": "From", "FullSeason": "Full Season", "General": "General", + "GeneralSettings": "General Settings", + "Health": "Health", "HiddenClickToShow": "Hidden, click to show", "HideAdvanced": "Hide Advanced", "History": "History", + "HomePage": "Home Page", "Implementation": "Implementation", "ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}", "ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}", + "ImportLists": "Import Lists", "ImportListStatusAllUnavailableHealthCheckMessage": "All lists are unavailable due to failures", "ImportListStatusUnavailableHealthCheckMessage": "Lists unavailable due to failures: {0}", - "ImportLists": "Import Lists", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleHealthCheckMessage": "Enable Completed Download Handling if possible", "ImportMechanismEnableCompletedDownloadHandlingIfPossibleMultiComputerHealthCheckMessage": "Enable Completed Download Handling if possible (Multi-Computer unsupported)", "ImportMechanismHandlingDisabledHealthCheckMessage": "Enable Completed Download Handling", @@ -96,47 +130,74 @@ "IndexerLongTermStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures for more than 6 hours: {0}", "IndexerRssNoIndexersAvailableHealthCheckMessage": "All rss-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerRssNoIndexersEnabledHealthCheckMessage": "No indexers available with RSS sync enabled, Sonarr will not grab new releases automatically", + "Indexers": "Indexers", "IndexerSearchNoAutomaticHealthCheckMessage": "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results", "IndexerSearchNoAvailableIndexersHealthCheckMessage": "All search-capable indexers are temporarily unavailable due to recent indexer errors", "IndexerSearchNoInteractiveHealthCheckMessage": "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results", "IndexerStatusAllUnavailableHealthCheckMessage": "All indexers are unavailable due to failures", "IndexerStatusUnavailableHealthCheckMessage": "Indexers unavailable due to failures: {0}", - "Indexers": "Indexers", + "InstallLatest": "Install Latest", + "Interval": "Interval", + "IRC": "IRC", "Language": "Language", "Language that Sonarr will use for UI": "Language that Sonarr will use for UI", "Languages": "Languages", + "LastDuration": "Last Duration", + "LastExecution": "Last Execution", + "LastWriteTime": "Last Write Time", "LibraryImport": "Library Import", + "Location": "Location", "LogFiles": "Log Files", + "LogFilesLocation": "Log files are located in: {location}", + "Logs": "Logs", + "MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details", "ManageClients": "Manage Clients", "ManageDownloadClients": "Manage Download Clients", "ManageImportLists": "Manage Import Lists", "ManageIndexers": "Manage Indexers", "ManageLists": "Manage Lists", + "Manual": "Manual", "MatchedToEpisodes": "Matched to Episodes", "MatchedToSeason": "Matched to Season", "MatchedToSeries": "Matched to Series", "MediaManagement": "Media Management", + "Message": "Message", "Metadata": "Metadata", "MetadataSource": "Metadata Source", "Missing": "Missing", + "Mode": "Mode", "Monitored": "Monitored", + "MoreInfo": "More Info", "MountHealthCheckMessage": "Mount containing a series path is mounted read-only: ", "MultiSeason": "Multi-Season", "Name": "Name", "Negated": "Negated", "Network": "Network", + "New": "New", "NextAiring": "Next Airing", + "NextExecution": "Next Execution", "No": "No", + "NoBackupsAreAvailable": "No backups are available", "NoChange": "No Change", "NoDownloadClientsFound": "No download clients found", + "NoEventsFound": "No events found", "NoImportListsFound": "No import lists found", "NoIndexersFound": "No indexers found", + "NoIssuesWithYourConfiguration": "No issues with your configuration", + "NoLeaveIt": "No, Leave It", + "NoLogFiles": "No log files", "NoSeasons": "No seasons", + "NoUpdatesAreAvailable": "No updates are available", "OneSeason": "1 season", + "OnLatestVersion": "The latest version of Radarr is already installed", + "Options": "Options", "OriginalLanguage": "Original Language", + "PackageVersion": "Package Version", + "PackageVersionInfo": "{packageVersion} by {packageAuthor}", "PartialSeason": "Partial Season", "Path": "Path", "PreviousAiring": "Previous Airing", + "PreviouslyInstalled": "Previously Installed", "Priority": "Priority", "Profiles": "Profiles", "Proper": "Proper", @@ -146,13 +207,17 @@ "Quality": "Quality", "QualityProfile": "Quality Profile", "Queue": "Queue", + "Queued": "Queued", + "ReadTheWikiForMoreInformation": "Read the Wiki for more information", "Real": "Real", "RecycleBinUnableToWriteHealthCheckMessage": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Sonarr", + "Refresh": "Refresh", "RefreshSeries": "Refresh Series", "Release": "Release", "ReleaseGroup": "Release Group", "ReleaseHash": "Release Hash", "ReleaseTitle": "Release Title", + "Reload": "Reload", "RemotePathMappingBadDockerPathHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this is not a valid {2} path. Review your remote path mappings and download client settings.", "RemotePathMappingDockerFolderMissingHealthCheckMessage": "You are using docker; download client {0} places downloads in {1} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "RemotePathMappingDownloadPermissionsHealthCheckMessage": "Sonarr can see but not access downloaded episode {0}. Likely permissions error.", @@ -171,6 +236,9 @@ "Remove": "Remove", "RemoveCompleted": "Remove Completed", "RemoveCompletedDownloads": "Remove Completed Downloads", + "RemovedFromTaskQueue": "Removed from task queue", + "RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB", + "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB", "RemoveFailed": "Remove Failed", "RemoveFailedDownloads": "Remove Failed Downloads", "RemoveFromDownloadClient": "Remove From Download Client", @@ -179,40 +247,62 @@ "RemoveSelectedItemQueueMessageText": "Are you sure you want to remove 1 item from the queue?", "RemoveSelectedItems": "Remove Selected Items", "RemoveSelectedItemsQueueMessageText": "Are you sure you want to remove {0} items from the queue?", - "RemovedSeriesMultipleRemovedHealthCheckMessage": "Series {0} were removed from TheTVDB", - "RemovedSeriesSingleRemovedHealthCheckMessage": "Series {0} was removed from TheTVDB", "RemovingTag": "Removing tag", "Repack": "Repack", "Replace": "Replace", "Required": "Required", + "Restart": "Restart", + "RestartReloadNote": "Note: Sonarr will automatically restart and reload the UI during the restore process.", + "Restore": "Restore", + "RestoreBackup": "Restore Backup", "Result": "Result", "RootFolder": "Root Folder", "RootFolderMissingHealthCheckMessage": "Missing root folder: {0}", "RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}", + "Scheduled": "Scheduled", "SearchForMonitoredEpisodes": "Search for monitored episodes", "SeasonNumber": "Season Number", "Series": "Series", + "SeriesEditor": "Series Editor", "SeriesTitle": "Series Title", "SetTags": "Set Tags", "Settings": "Settings", "ShowAdvanced": "Show Advanced", "ShownClickToHide": "Shown, click to hide", + "Size": "Size", "SizeOnDisk": "Size on disk", + "Source": "Source", "Special": "Special", + "Started": "Started", + "StartupDirectory": "Startup directory", + "Status": "Status", "System": "System", "SystemTimeHealthCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", "Tags": "Tags", "Tasks": "Tasks", + "TaskUserAgentTooltip": "User-Agent provided by the app that called the API", + "TestAll": "Test All", "TestParsing": "Test Parsing", + "TheLogLevelDefault": "The log level defaults to 'Info' and can be changed in [General Settings](/settings/general)", + "Time": "Time", + "TotalSpace": "Total Space", + "Twitter": "Twitter", "UI": "UI", "UI Language": "UI Language", + "UnableToLoadBackups": "Unable to load backups", + "UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,", "Unmonitored": "Unmonitored", "UpdateAvailableHealthCheckMessage": "New update is available", + "UpdaterLogFiles": "Updater Log Files", + "Updates": "Updates", "UpdateStartupNotWritableHealthCheckMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", "UpdateStartupTranslocationHealthCheckMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.", "UpdateUINotWritableHealthCheckMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.", - "Updates": "Updates", + "Uptime": "Uptime", "Version": "Version", "Wanted": "Wanted", - "Yes": "Yes" + "Wiki": "Wiki", + "WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?", + "Yes": "Yes", + "YesCancel": "Yes, Cancel" } From 5f90fdd998c6879114b0db1cee6ed0bd49d6374c Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 20 Jul 2023 04:22:31 +0300 Subject: [PATCH 6/9] Add missing translations for Reset Quality Definitions modal --- src/NzbDrone.Core/Localization/Core/en.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 1a3eea420..31d7cfe44 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -251,6 +251,11 @@ "Repack": "Repack", "Replace": "Replace", "Required": "Required", + "Reset": "Reset", + "ResetDefinitionTitlesHelpText": "Reset definition titles as well as values", + "ResetQualityDefinitions": "Reset Quality Definitions", + "ResetQualityDefinitionsMessageText": "Are you sure you want to reset quality definitions?", + "ResetTitles": "Reset Titles", "Restart": "Restart", "RestartReloadNote": "Note: Sonarr will automatically restart and reload the UI during the restore process.", "Restore": "Restore", From bb67c30dd5a9bfdf5e8f5045a18c8f9512ad5667 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 20 Jul 2023 00:52:47 +0000 Subject: [PATCH 7/9] Multiple Translations updated by Weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ignore-downstream Co-authored-by: Deamon1333 Co-authored-by: Godwhitelight Co-authored-by: Guy Porat Co-authored-by: Havok Dan Co-authored-by: Leliene Co-authored-by: Weblate Co-authored-by: liimee Co-authored-by: 君禹渊 Co-authored-by: 無情天 Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/de/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/fr/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/he/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/id/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/pt_BR/ Translate-URL: https://translate.servarr.com/projects/servarr/sonarr/zh_CN/ Translation: Servarr/Sonarr --- src/NzbDrone.Core/Localization/Core/de.json | 20 ++- src/NzbDrone.Core/Localization/Core/fr.json | 8 +- src/NzbDrone.Core/Localization/Core/he.json | 4 +- src/NzbDrone.Core/Localization/Core/hu.json | 1 - src/NzbDrone.Core/Localization/Core/id.json | 40 +++++- .../Localization/Core/pt_BR.json | 117 ++++++++++++++++-- src/NzbDrone.Core/Localization/Core/ru.json | 1 - .../Localization/Core/zh_CN.json | 35 +++++- 8 files changed, 208 insertions(+), 18 deletions(-) diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index 503f5738b..e9cf1d13c 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -14,9 +14,9 @@ "DownloadClientSortingHealthCheckMessage": "Im Download-Client {0} ist die Sortierung {1} für die Kategorie von Sonarr aktiviert. Sie sollten die Sortierung in Ihrem Download-Client deaktivieren, um Importprobleme zu vermeiden.", "DownloadClientStatusSingleClientHealthCheckMessage": "Download-Clients sind aufgrund von Fehlern nicht verfügbar: {0}", "DownloadClientStatusAllClientHealthCheckMessage": "Alle Download-Clients sind aufgrund von Fehlern nicht verfügbar", - "EditSelectedDownloadClients": "Ausgewählte Download-Clients bearbeiten", - "EditSelectedImportLists": "Ausgewählte Importlisten bearbeiten", - "EditSelectedIndexers": "Ausgewählte Indexer bearbeiten", + "EditSelectedDownloadClients": "Ausgewählte Download Clienten bearbeiten", + "EditSelectedImportLists": "Ausgewählte Einspiel-Liste bearbeten", + "EditSelectedIndexers": "Ausgewähle Indexer bearbeiten", "EditSeries": "Serie bearbeiten", "EnableAutomaticSearch": "Automatische Suche einschalten", "EnableInteractiveSearch": "Interaktive Suche einschalten", @@ -26,5 +26,17 @@ "DeleteConditionMessageText": "Bist du sicher, dass du die Bedingung '{0}' löschen willst?", "DeleteCustomFormatMessageText": "Bist du sicher, dass du das eigene Format '{0}' löschen willst?", "RemoveSelectedItemQueueMessageText": "Bist du sicher, dass du ein Eintrag aus der Warteschlange entfernen willst?", - "RemoveSelectedItemsQueueMessageText": "Bist du sicher, dass du {0} Einträge aus der Warteschlange entfernen willst?" + "RemoveSelectedItemsQueueMessageText": "Bist du sicher, dass du {0} Einträge aus der Warteschlange entfernen willst?", + "DeleteSelectedDownloadClients": "Lösche Download Client(s)", + "DeleteSelectedIndexers": "Lösche Indexer", + "DeleteSelectedImportLists": "Lösche Einspiel Liste", + "Implementation": "Integration", + "ManageIndexers": "Verwalte Indexer", + "ManageLists": "Verwalte Listen", + "NoImportListsFound": "Keine Einspiel-Listen gefunden", + "NoIndexersFound": "Keine Indexer gefunden", + "ManageClients": "Verwalte Clienten", + "ManageDownloadClients": "Verwalte Download Clienten", + "ManageImportLists": "Verwalte Einspiel-Listen", + "NoDownloadClientsFound": "Keine Download Clienten gefunden" } diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index baaeec6d4..61da69911 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -3,11 +3,15 @@ "UI Language": "UI Langue", "Language that Sonarr will use for UI": "Langue que Sonarr utilisera pour l'interface utilisateur", "Browser Reload Required": "Rechargement du navigateur requis", - "Added": "Ajouter", + "Added": "Ajouté", "ApiKeyValidationHealthCheckMessage": "Veuillez mettre à jour votre clé API pour qu'elle contienne au moins {0} caractères. Vous pouvez le faire via les paramètres ou le fichier de configuration", "AppDataLocationHealthCheckMessage": "La mise à jour ne sera pas possible pour empêcher la suppression de AppData lors de la mise à jour", "ApplyChanges": "Appliquer les modifications", "AutomaticAdd": "Ajout automatique", "CountSeasons": "{count} saisons", - "DownloadClientCheckNoneAvailableHealthCheckMessage": "Aucun client de téléchargement disponible" + "DownloadClientCheckNoneAvailableHealthCheckMessage": "Aucun client de téléchargement disponible", + "Add": "Ajouter", + "AddingTag": "Ajout d'un tag", + "Apply": "Appliquer", + "ApplyTags": "Appliquer les tags" } diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index 52cbf6691..9efdba569 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -1,4 +1,6 @@ { "Added": "נוסף", - "ApiKeyValidationHealthCheckMessage": "עדכן בבקשה את מפתח ה API שלך שיהיה עם לפחות {0} תווים. ניתן לעשות זאת דרך ההגדות או קובץ הקונפיגורציה" + "ApiKeyValidationHealthCheckMessage": "עדכן בבקשה את מפתח ה־API שלך כדי שיהיה באורך של לפחות {0} תווים. תוכל לעשות זאת בהגדרות או דרך קובץ הקונפיגורציה.", + "Add": "הוסף", + "Activity": "פעילות" } diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index e70656414..ee5d3acc1 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -35,7 +35,6 @@ "DownloadClientStatusSingleClientHealthCheckMessage": "Letöltési kliensek elérhetetlenek meghibásodások miatt: {0}", "EnableAutomaticSearch": "Automatikus keresés engedélyezése", "EditSeries": "Sorozat szerkesztése", - "EnableRss": "RSS engedélyezése", "EnableInteractiveSearch": "Interaktív keresés engedélyezése", "Ended": "Vége", "HideAdvanced": "Haladó elrejtése", diff --git a/src/NzbDrone.Core/Localization/Core/id.json b/src/NzbDrone.Core/Localization/Core/id.json index 13ca72fe3..f30d18dad 100644 --- a/src/NzbDrone.Core/Localization/Core/id.json +++ b/src/NzbDrone.Core/Localization/Core/id.json @@ -1,3 +1,41 @@ { - "Added": "Ditambahkan" + "Added": "Ditambahkan", + "BlocklistReleaseHelpText": "Mencegah Sonarr memperoleh rilis ini secara otomatis", + "Delete": "Hapus", + "Close": "Tutup", + "Language that Sonarr will use for UI": "Bahasa yang digunakan UI Sonarr", + "EnableAutomaticSearch": "Aktifkan Penelusuran Otomatis", + "EnableInteractiveSearch": "Aktifkan Penelusuran Interaktif", + "Enabled": "Aktif", + "Language": "Bahasa", + "Network": "Jaringan", + "NextAiring": "Tayang Selanjutnya", + "Monitored": "Dimonitor", + "Path": "Path", + "PreviousAiring": "Sebelumnya Tayang", + "OriginalLanguage": "Bahasa Asli", + "Priority": "Prioritas", + "ProxyFailedToTestHealthCheckMessage": "Gagal menguji proxy: {0}", + "ProxyBadRequestHealthCheckMessage": "Gagal menguji proxy. Kode Status: {0}", + "QualityProfile": "Profil Kualitas", + "Add": "Tambah", + "Cancel": "Batal", + "NoSeasons": "Tidak ada musim", + "OneSeason": "1 musim", + "Disabled": "Nonaktif", + "Activity": "Aktivitas", + "Calendar": "Kalender", + "EnableRSS": "Aktifkan RSS", + "Ended": "Berakhir", + "LibraryImport": "Impor Pustaka", + "LogFiles": "Berkas Log", + "MediaManagement": "Pengelolaan Media", + "Metadata": "Metadata", + "MetadataSource": "Sumber Metadata", + "Name": "Nama", + "No": "Tidak", + "NoChange": "Tidak Ada Perubahan", + "Profiles": "Profil", + "Quality": "Kualitas", + "Queue": "Antrean" } diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 073019bd0..881648a48 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -7,7 +7,6 @@ "DownloadClientStatusAllClientHealthCheckMessage": "Todos os clientes de download estão indisponíveis devido a falhas", "EditSelectedDownloadClients": "Editar clientes de download selecionados", "EditSelectedImportLists": "Editar listas de importação selecionadas", - "EnableRss": "Ativar Rss", "Enabled": "Habilitado", "Ended": "Terminou", "HideAdvanced": "Ocultar Avançado", @@ -92,14 +91,14 @@ "UpdateStartupNotWritableHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' não pode ser gravada pelo usuário '{1}'.", "UpdateStartupTranslocationHealthCheckMessage": "Não é possível instalar a atualização porque a pasta de inicialização '{0}' está em uma pasta de translocação de aplicativo.", "BlocklistReleaseHelpText": "Impede que o Sonarr pegue automaticamente esta versão novamente", - "BlocklistReleases": "Lista de Bloqueio de Lançamentos", - "CloneCondition": "Condição de Clone", - "CloneCustomFormat": "Clonar Formato Personalizado", + "BlocklistReleases": "Lançamentos na lista de bloqueio", + "CloneCondition": "Condição de clone", + "CloneCustomFormat": "Clonar formato personalizado", "Close": "Fechar", "Delete": "Excluir", - "DeleteCondition": "Excluir Condição", + "DeleteCondition": "Excluir condição", "DeleteConditionMessageText": "Tem certeza de que deseja excluir a condição '{0}'?", - "DeleteCustomFormat": "Excluir Formato Personalizado", + "DeleteCustomFormat": "Excluir formato personalizado", "DeleteCustomFormatMessageText": "Tem certeza de que deseja excluir o formato personalizado '{0}'?", "ExportCustomFormat": "Exportar Formato Personalizado", "Negated": "Negado", @@ -111,5 +110,109 @@ "RemoveSelectedItems": "Remover Itens Selecionados", "RemoveSelectedItemsQueueMessageText": "Tem certeza de que deseja remover {0} itens da fila?", "Required": "Requerido", - "BlocklistRelease": "Lista de Bloqueio de Lançamentos" + "BlocklistRelease": "Lançamento na lista de bloqueio", + "Add": "Adicionar", + "AddingTag": "Adicionar tag", + "Apply": "Aplicar", + "ApplyTags": "Aplicar Tags", + "Cancel": "Cancelar", + "CountDownloadClientsSelected": "{count} cliente(s) de download selecionado(s)", + "CountImportListsSelected": "{count} lista(s) de importação selecionada(s)", + "CountIndexersSelected": "{count} indexador(es) selecionado(s)", + "DeleteSelectedDownloadClients": "Excluir cliente(s) de download", + "DeleteSelectedImportLists": "Excluir lista(s) de importação", + "DeleteSelectedIndexers": "Excluir indexador(es)", + "DeleteSelectedDownloadClientsMessageText": "Tem certeza de que deseja excluir {count} cliente(s) de download selecionado(s)?", + "DeleteSelectedImportListsMessageText": "Tem certeza de que deseja excluir {count} lista(s) de importação selecionada(s)?", + "ExistingTag": "Etiqueta existente", + "Implementation": "Implementação", + "Disabled": "Desabilitado", + "Edit": "Editar", + "EnableRSS": "Habilitar RSS", + "ManageClients": "Gerenciar clientes", + "ManageIndexers": "Gerenciar indexadores", + "ManageDownloadClients": "Gerenciar clientes de download", + "ManageImportLists": "Gerenciar listas de importação", + "No": "Não", + "ManageLists": "Gerenciar listas", + "Name": "Nome", + "NoChange": "Sem alteração", + "NoDownloadClientsFound": "Nenhum cliente de download encontrado", + "NoImportListsFound": "Nenhuma lista de importação encontrada", + "NoIndexersFound": "Nenhum indexador encontrado", + "RemoveFailed": "Falha na remoção", + "Replace": "Substituir", + "Result": "Resultado", + "SetTags": "Definir etiquetas", + "Yes": "Sim", + "Tags": "Tags", + "AutoAdd": "Adicionar automaticamente", + "RemovingTag": "Removendo etiqueta", + "DeleteSelectedIndexersMessageText": "Tem certeza de que deseja excluir {count} indexadores selecionados?", + "RemoveCompleted": "Remoção Concluída", + "LibraryImport": "Importar para biblioteca", + "LogFiles": "Arquivos de registro", + "MediaManagement": "Gerenciamento de Mídia", + "Metadata": "Metadados", + "MetadataSource": "Fonte de Metadados", + "Missing": "Ausente", + "Profiles": "Perfis", + "Quality": "Qualidade", + "Queue": "Fila", + "Series": "Séries", + "Settings": "Configurações", + "System": "Sistema", + "Tasks": "Tarefas", + "UI": "UI", + "Updates": "Atualizações", + "Wanted": "Procurado", + "ApplyTagsHelpTextHowToApply": "Como aplicar tags nos indexadores selecionados", + "ApplyTagsHelpTextAdd": "Adicionar: adicione as etiquetas à lista existente de etiquetas", + "ApplyTagsHelpTextReplace": "Substituir: Substitua as etiquetas pelas etiquetas inseridas (não digite nenhuma etiqueta para limpar todas as etiquetas)", + "ApplyTagsHelpTextRemove": "Remover: remove as etiquetas inseridas", + "CustomFormatScore": "Pontuação do formato personalizado", + "Activity": "Atividade", + "AddNew": "Adicionar Novo", + "Backup": "Backup", + "Blocklist": "Lista de Bloqueio", + "Calendar": "Calendário", + "Connect": "Conectar", + "CustomFormats": "Formatos personalizados", + "CutoffUnmet": "Corte não alcançado", + "DownloadClients": "Clientes de download", + "Events": "Eventos", + "General": "Geral", + "History": "Histórico", + "ImportLists": "Listas de importação", + "Indexers": "Indexadores", + "AbsoluteEpisodeNumbers": "Número(s) absoluto(s) do episódio", + "AirDate": "Data de Exibição", + "Daily": "Diário", + "Details": "Detalhes", + "AllTitles": "Todos os Títulos", + "Version": "Versão", + "ApplyTagsHelpTextHowToApplyDownloadClients": "Como aplicar tags aos clientes de download selecionados", + "ApplyTagsHelpTextHowToApplyImportLists": "Como aplicar tags às listas de importação selecionadas", + "ApplyTagsHelpTextHowToApplyIndexers": "Como aplicar tags aos indexadores selecionados", + "ApplyTagsHelpTextHowToApplySeries": "Como aplicar tags à série selecionada", + "EpisodeInfo": "Info do Episódio", + "EpisodeNumbers": "Número(s) do(s) Episódio(s)", + "FullSeason": "Temporada Completa", + "Languages": "Idiomas", + "MatchedToEpisodes": "Correspondente aos Episódios", + "MatchedToSeason": "Correspondente a Temporada", + "MatchedToSeries": "Correspondente à Série", + "MultiSeason": "Multi-Temporada", + "PartialSeason": "Temporada Parcial", + "Proper": "Proper", + "Real": "Real", + "Release": "Lançamento", + "ReleaseGroup": "Grupo do Lançamento", + "ReleaseHash": "Hash do Lançamento", + "ReleaseTitle": "Título do Lançamento", + "Repack": "Repack", + "SeasonNumber": "Número da Temporada", + "SeriesTitle": "Título da Série", + "Special": "Especial", + "TestParsing": "Análise de Teste" } diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index 4f2fc743a..9cdf4829e 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -17,7 +17,6 @@ "EditSeries": "Редактировать серию", "EnableAutomaticSearch": "Включить автоматический поиск", "EnableInteractiveSearch": "Включить интерактивный поиск", - "EnableRss": "Включить RSS", "Enabled": "Включено", "HiddenClickToShow": "Скрыто, нажмите чтобы показать", "HideAdvanced": "Скрыть расширенные", diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 501060179..8c07b9b47 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -3,5 +3,38 @@ "DeleteCondition": "删除条件", "DeleteConditionMessageText": "是否确实要删除条件“{0}”?", "DeleteCustomFormatMessageText": "是否确实要删除条件“{0}”?", - "ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少{0}个字符长。您可以通过设置或配置文件执行此操作" + "ApiKeyValidationHealthCheckMessage": "请将API密钥更新为至少{0}个字符长。您可以通过设置或配置文件执行此操作", + "RemoveSelectedItemQueueMessageText": "您确定要从队列中删除 1 项吗?", + "RemoveSelectedItemsQueueMessageText": "您确定要从队列中删除 {0} 个项目吗?", + "ApplyChanges": "应用更改", + "AutomaticAdd": "自动添加", + "Browser Reload Required": "需要重新加载浏览器", + "EditSelectedDownloadClients": "编辑选定的下载客户端", + "EditSelectedIndexers": "编辑选定的索引器", + "EditSelectedImportLists": "编辑选定的导入列表", + "HiddenClickToShow": "隐藏,点击显示", + "HideAdvanced": "隐藏高级", + "Language": "语言", + "RemoveCompletedDownloads": "删除已完成的下载", + "RemoveFailedDownloads": "删除失败的下载", + "ShowAdvanced": "显示高级", + "ShownClickToHide": "显示,点击隐藏", + "UI Language": "用户界面语言", + "DeleteSelectedDownloadClients": "删除下载客户端", + "DeleteSelectedImportLists": "删除导入列表", + "DeleteSelectedIndexers": "删除索引器", + "Implementation": "执行", + "ManageClients": "管理客户", + "ManageDownloadClients": "管理下载客户端", + "ManageImportLists": "管理导入列表", + "ManageIndexers": "管理索引器", + "ManageLists": "管理列表", + "NoDownloadClientsFound": "找不到下载客户端", + "NoImportListsFound": "未找到导入列表", + "NoIndexersFound": "未找到索引器", + "Added": "已添加", + "Add": "添加", + "AddingTag": "添加标签", + "Apply": "应用", + "ApplyTags": "应用标签" } From dee8820b1f31e9180c55c6d29b950ff6cfe0205f Mon Sep 17 00:00:00 2001 From: Sonarr Date: Wed, 19 Jul 2023 20:31:40 +0000 Subject: [PATCH 8/9] Automated API Docs update ignore-downstream --- src/Sonarr.Api.V3/openapi.json | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index 15432ae5a..f585f2877 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -3327,18 +3327,6 @@ } } }, - "/initialize.js": { - "get": { - "tags": [ - "InitializeJs" - ], - "responses": { - "200": { - "description": "Success" - } - } - } - }, "/api/v3/language": { "get": { "tags": [ @@ -9025,6 +9013,10 @@ }, "nullable": true }, + "customFormatScore": { + "type": "integer", + "format": "int32" + }, "rejections": { "type": "array", "items": { From 38f263931ff8faba050762abe5fb692a5bc0d515 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 20 Jul 2023 20:05:05 +0300 Subject: [PATCH 9/9] Cache busting for CSS files --- frontend/build/webpack.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/build/webpack.config.js b/frontend/build/webpack.config.js index 733e2bc4d..e0ec27c27 100644 --- a/frontend/build/webpack.config.js +++ b/frontend/build/webpack.config.js @@ -91,7 +91,8 @@ module.exports = (env) => { }), new MiniCssExtractPlugin({ - filename: 'Content/styles.css' + filename: 'Content/styles.css', + chunkFilename: 'Content/[id]-[chunkhash].css' }), new HtmlWebpackPlugin({