From 31261f66ad7a186f5d179c2b318e9a6b0ac934b9 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 13 Aug 2023 21:03:38 +0300 Subject: [PATCH] Use named tokens in frontend translate function --- .../src/Indexer/Add/AddIndexerModalContent.js | 2 +- .../Delete/DeleteIndexerModalContent.tsx | 2 +- .../Delete/DeleteIndexerModalContent.tsx | 4 +- .../Select/Edit/EditIndexerModalContent.tsx | 2 +- .../Index/Select/IndexerIndexSelectFooter.tsx | 2 +- frontend/src/Search/SearchFooter.js | 4 +- .../Applications/Applications/Application.js | 2 +- .../ManageApplicationsEditModalContent.tsx | 2 +- .../Manage/ManageApplicationsModalContent.tsx | 6 +-- .../DownloadClients/Categories/Category.js | 2 +- .../DownloadClients/DownloadClient.js | 2 +- .../ManageDownloadClientsEditModalContent.tsx | 2 +- .../ManageDownloadClientsModalContent.tsx | 6 +-- .../Indexers/IndexerProxies/IndexerProxy.js | 2 +- .../Notifications/Notification.js | 2 +- .../src/Settings/Profiles/App/AppProfile.js | 2 +- frontend/src/Settings/Tags/Tag.js | 2 +- frontend/src/Settings/UI/UISettings.js | 2 +- frontend/src/System/Backup/BackupRow.js | 2 +- frontend/src/System/Updates/Updates.js | 2 +- frontend/src/Utilities/String/translate.ts | 13 ++++-- src/NzbDrone.Core/Localization/Core/en.json | 43 +++++++++---------- 22 files changed, 57 insertions(+), 51 deletions(-) diff --git a/frontend/src/Indexer/Add/AddIndexerModalContent.js b/frontend/src/Indexer/Add/AddIndexerModalContent.js index 7925167e6..7d205dc11 100644 --- a/frontend/src/Indexer/Add/AddIndexerModalContent.js +++ b/frontend/src/Indexer/Add/AddIndexerModalContent.js @@ -288,7 +288,7 @@ class AddIndexerModalContent extends Component {
{ isPopulated ? - translate('CountIndexersAvailable', [filteredIndexers.length]) : + translate('CountIndexersAvailable', { count: filteredIndexers.length }) : null }
diff --git a/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx b/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx index 4052d13bc..aeae273a9 100644 --- a/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx +++ b/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx @@ -37,7 +37,7 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) { - {translate('AreYouSureYouWantToDeleteIndexer', [name])} + {translate('AreYouSureYouWantToDeleteIndexer', { name })} diff --git a/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx b/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx index 6a972b0d9..0793af82d 100644 --- a/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx +++ b/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx @@ -48,7 +48,9 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
- {translate('DeleteSelectedIndexersMessageText', [indexers.length])} + {translate('DeleteSelectedIndexersMessageText', { + count: indexers.length, + })}
    diff --git a/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx b/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx index a25a15d2f..b0bba5b94 100644 --- a/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx +++ b/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx @@ -257,7 +257,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
    - {translate('CountIndexersSelected', [selectedCount])} + {translate('CountIndexersSelected', { count: selectedCount })}
    diff --git a/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx b/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx index fe3082d0b..64fe8c1cb 100644 --- a/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx +++ b/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx @@ -165,7 +165,7 @@ function IndexerIndexSelectFooter() {
    - {translate('CountIndexersSelected', [selectedCount])} + {translate('CountIndexersSelected', { count: selectedCount })}
    - {translate('CountApplicationsSelected', [selectedCount])} + {translate('CountApplicationsSelected', { count: selectedCount })}
    diff --git a/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx b/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx index b3235aa32..8c45cf8a5 100644 --- a/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx +++ b/frontend/src/Settings/Applications/Applications/Manage/ManageApplicationsModalContent.tsx @@ -268,9 +268,9 @@ function ManageApplicationsModalContent( isOpen={isDeleteModalOpen} kind={kinds.DANGER} title={translate('DeleteSelectedApplications')} - message={translate('DeleteSelectedApplicationsMessageText', [ - selectedIds.length, - ])} + message={translate('DeleteSelectedApplicationsMessageText', { + count: selectedIds.length, + })} confirmLabel={translate('Delete')} onConfirm={onConfirmDelete} onCancel={onDeleteModalClose} diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Categories/Category.js b/frontend/src/Settings/DownloadClients/DownloadClients/Categories/Category.js index 6e0a25a2d..1d1f61469 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Categories/Category.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Categories/Category.js @@ -88,7 +88,7 @@ class Category extends Component { message={
    - {translate('AreYouSureYouWantToDeleteCategory', [name])} + {translate('AreYouSureYouWantToDeleteCategory')}
    } diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js index 7136f531e..13b24343d 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js @@ -105,7 +105,7 @@ class DownloadClient extends Component { isOpen={this.state.isDeleteDownloadClientModalOpen} kind={kinds.DANGER} title={translate('DeleteDownloadClient')} - message={translate('DeleteDownloadClientMessageText', [name])} + message={translate('DeleteDownloadClientMessageText', { name })} confirmLabel={translate('Delete')} onConfirm={this.onConfirmDeleteDownloadClient} onCancel={this.onDeleteDownloadClientModalClose} diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx index 5770a6255..a2e0f89c6 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/Edit/ManageDownloadClientsEditModalContent.tsx @@ -129,7 +129,7 @@ function ManageDownloadClientsEditModalContent(
    - {translate('CountDownloadClientsSelected', [selectedCount])} + {translate('CountDownloadClientsSelected', { count: selectedCount })}
    diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx index a6a4b501f..8e257ae7a 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx +++ b/frontend/src/Settings/DownloadClients/DownloadClients/Manage/ManageDownloadClientsModalContent.tsx @@ -226,9 +226,9 @@ function ManageDownloadClientsModalContent( isOpen={isDeleteModalOpen} kind={kinds.DANGER} title={translate('DeleteSelectedDownloadClients')} - message={translate('DeleteSelectedDownloadClientsMessageText', [ - selectedIds.length, - ])} + message={translate('DeleteSelectedDownloadClientsMessageText', { + count: selectedIds.length, + })} confirmLabel={translate('Delete')} onConfirm={onConfirmDelete} onCancel={onDeleteModalClose} diff --git a/frontend/src/Settings/Indexers/IndexerProxies/IndexerProxy.js b/frontend/src/Settings/Indexers/IndexerProxies/IndexerProxy.js index 84292ae65..dcd7e1f35 100644 --- a/frontend/src/Settings/Indexers/IndexerProxies/IndexerProxy.js +++ b/frontend/src/Settings/Indexers/IndexerProxies/IndexerProxy.js @@ -122,7 +122,7 @@ class IndexerProxy extends Component { isOpen={this.state.isDeleteIndexerProxyModalOpen} kind={kinds.DANGER} title={translate('DeleteIndexerProxy')} - message={translate('DeleteIndexerProxyMessageText', [name])} + message={translate('DeleteIndexerProxyMessageText', { name })} confirmLabel={translate('Delete')} onConfirm={this.onConfirmDeleteIndexerProxy} onCancel={this.onDeleteIndexerProxyModalClose} diff --git a/frontend/src/Settings/Notifications/Notifications/Notification.js b/frontend/src/Settings/Notifications/Notifications/Notification.js index 0ffd49b40..4ecf33047 100644 --- a/frontend/src/Settings/Notifications/Notifications/Notification.js +++ b/frontend/src/Settings/Notifications/Notifications/Notification.js @@ -137,7 +137,7 @@ class Notification extends Component { isOpen={this.state.isDeleteNotificationModalOpen} kind={kinds.DANGER} title={translate('DeleteNotification')} - message={translate('DeleteNotificationMessageText', [name])} + message={translate('DeleteNotificationMessageText', { name })} confirmLabel={translate('Delete')} onConfirm={this.onConfirmDeleteNotification} onCancel={this.onDeleteNotificationModalClose} diff --git a/frontend/src/Settings/Profiles/App/AppProfile.js b/frontend/src/Settings/Profiles/App/AppProfile.js index 546006ff5..db123d006 100644 --- a/frontend/src/Settings/Profiles/App/AppProfile.js +++ b/frontend/src/Settings/Profiles/App/AppProfile.js @@ -130,7 +130,7 @@ class AppProfile extends Component { isOpen={this.state.isDeleteAppProfileModalOpen} kind={kinds.DANGER} title={translate('DeleteAppProfile')} - message={translate('AppProfileDeleteConfirm', [name])} + message={translate('DeleteAppProfileMessageText', { name })} confirmLabel={translate('Delete')} isSpinning={isDeleting} onConfirm={this.onConfirmDeleteAppProfile} diff --git a/frontend/src/Settings/Tags/Tag.js b/frontend/src/Settings/Tags/Tag.js index 607a67543..afeb3863a 100644 --- a/frontend/src/Settings/Tags/Tag.js +++ b/frontend/src/Settings/Tags/Tag.js @@ -137,7 +137,7 @@ class Tag extends Component { isOpen={isDeleteTagModalOpen} kind={kinds.DANGER} title={translate('DeleteTag')} - message={translate('DeleteTagMessageText', [label])} + message={translate('DeleteTagMessageText', { label })} confirmLabel={translate('Delete')} onConfirm={this.onConfirmDeleteTag} onCancel={this.onDeleteTagModalClose} diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js index 614fd2fe0..cfb7c04f6 100644 --- a/frontend/src/Settings/UI/UISettings.js +++ b/frontend/src/Settings/UI/UISettings.js @@ -147,7 +147,7 @@ class UISettings extends Component {
    - {translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])} + {translate('TheLatestVersionIsAlreadyInstalled', { appName: 'Prowlarr' })}
    { diff --git a/frontend/src/Utilities/String/translate.ts b/frontend/src/Utilities/String/translate.ts index 9c6fa778b..2ee319bc8 100644 --- a/frontend/src/Utilities/String/translate.ts +++ b/frontend/src/Utilities/String/translate.ts @@ -25,14 +25,19 @@ export async function fetchTranslations(): Promise { export default function translate( key: string, - args?: (string | number | boolean)[] + tokens?: Record ) { const translation = translations[key] || key; - if (args) { - return translation.replace(/\{(\d+)\}/g, (match, index) => { - return String(args[index]) ?? match; + if (tokens) { + // Fallback to the old behaviour for translations not yet updated to use named tokens + Object.values(tokens).forEach((value, index) => { + tokens[index] = value; }); + + return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) => + String(tokens[tokenMatch] ?? match) + ); } return translation; diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 8c267fcae..289b7103c 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -28,7 +28,6 @@ "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", - "AppProfileDeleteConfirm": "Are you sure you want to delete {0}?", "AppProfileInUse": "App Profile in Use", "AppProfileSelectHelpText": "App profiles are used to control RSS, Automatic Search and Interactive Search settings on application sync", "AppSettingsSummary": "Applications and settings to configure how Prowlarr interacts with your PVR programs", @@ -52,7 +51,7 @@ "AppsMinimumSeeders": "Apps Minimum Seeders", "AppsMinimumSeedersHelpText": "Minimum seeders required by the Applications for the indexer to grab, empty is Sync profile's default", "AreYouSureYouWantToDeleteCategory": "Are you sure you want to delete mapped category?", - "AreYouSureYouWantToDeleteIndexer": "Are you sure you want to delete '{0}' from Prowlarr?", + "AreYouSureYouWantToDeleteIndexer": "Are you sure you want to delete '{name}' from Prowlarr?", "AreYouSureYouWantToResetYourAPIKey": "Are you sure you want to reset your API Key?", "Artist": "Artist", "AudioSearch": "Audio Search", @@ -108,10 +107,10 @@ "ConnectionLostMessage": "Prowlarr has lost its connection to the backend and will need to be reloaded to restore functionality.", "Connections": "Connections", "CouldNotConnectSignalR": "Could not connect to SignalR, UI won't update", - "CountApplicationsSelected": "{0} application(s) selected", - "CountDownloadClientsSelected": "{0} download client(s) selected", - "CountIndexersAvailable": "{0} indexer(s) available", - "CountIndexersSelected": "{0} indexer(s) selected", + "CountApplicationsSelected": "{count} application(s) selected", + "CountDownloadClientsSelected": "{count} download client(s) selected", + "CountIndexersAvailable": "{count} indexer(s) available", + "CountIndexersSelected": "{count} indexer(s) selected", "Custom": "Custom", "CustomFilters": "Custom Filters", "DBMigration": "DB Migration", @@ -120,26 +119,27 @@ "Dates": "Dates", "Delete": "Delete", "DeleteAppProfile": "Delete App Profile", + "DeleteAppProfileMessageText": "Are you sure you want to delete the app profile '{name}'?", "DeleteApplication": "Delete Application", - "DeleteApplicationMessageText": "Are you sure you want to delete the application '{0}'?", + "DeleteApplicationMessageText": "Are you sure you want to delete the application '{name}'?", "DeleteBackup": "Delete Backup", - "DeleteBackupMessageText": "Are you sure you want to delete the backup '{0}'?", + "DeleteBackupMessageText": "Are you sure you want to delete the backup '{name}'?", "DeleteClientCategory": "Delete Download Client Category", "DeleteDownloadClient": "Delete Download Client", - "DeleteDownloadClientMessageText": "Are you sure you want to delete the download client '{0}'?", + "DeleteDownloadClientMessageText": "Are you sure you want to delete the download client '{name}'?", "DeleteIndexerProxy": "Delete Indexer Proxy", - "DeleteIndexerProxyMessageText": "Are you sure you want to delete the proxy '{0}'?", + "DeleteIndexerProxyMessageText": "Are you sure you want to delete the indexer proxy '{name}'?", "DeleteNotification": "Delete Notification", - "DeleteNotificationMessageText": "Are you sure you want to delete the notification '{0}'?", + "DeleteNotificationMessageText": "Are you sure you want to delete the notification '{name}'?", "DeleteSelectedApplications": "Delete Selected Applications", - "DeleteSelectedApplicationsMessageText": "Are you sure you want to delete {0} selected application(s)?", + "DeleteSelectedApplicationsMessageText": "Are you sure you want to delete {count} selected application(s)?", "DeleteSelectedDownloadClients": "Delete Download Client(s)", - "DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {0} selected download client(s)?", + "DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?", "DeleteSelectedIndexer": "Delete Selected Indexer", "DeleteSelectedIndexers": "Delete Selected Indexers", - "DeleteSelectedIndexersMessageText": "Are you sure you want to delete {0} selected indexer(s)?", + "DeleteSelectedIndexersMessageText": "Are you sure you want to delete {count} selected indexer(s)?", "DeleteTag": "Delete Tag", - "DeleteTagMessageText": "Are you sure you want to delete the tag '{0}'?", + "DeleteTagMessageText": "Are you sure you want to delete the tag '{label}'?", "Description": "Description", "Details": "Details", "DevelopmentSettings": "Development Settings", @@ -195,7 +195,7 @@ "FocusSearchBox": "Focus Search Box", "Folder": "Folder", "ForMoreInformationOnTheIndividualDownloadClients": "For more information on the individual download clients, click on the info buttons.", - "FoundCountReleases": "Found {0} releases", + "FoundCountReleases": "Found {itemCount} releases", "FullSync": "Full Sync", "General": "General", "GeneralSettings": "General Settings", @@ -257,7 +257,6 @@ "IndexerVipCheckExpiredClientMessage": "Indexer VIP benefits have expired: {0}", "IndexerVipCheckExpiringClientMessage": "Indexer VIP benefits expiring soon: {0}", "Indexers": "Indexers", - "IndexersSelectedInterp": "{0} Indexer(s) Selected", "Info": "Info", "InitialFailure": "Initial Failure", "InstanceName": "Instance Name", @@ -358,7 +357,7 @@ "Proxies": "Proxies", "Proxy": "Proxy", "ProxyBypassFilterHelpText": "Use ',' as a separator, and '*.' as a wildcard for subdomains", - "ProxyCheckBadRequestMessage": "Failed to test proxy. StatusCode: {0}", + "ProxyCheckBadRequestMessage": "Failed to test proxy. Status code: {0}", "ProxyCheckFailedToTestMessage": "Failed to test proxy: {0}", "ProxyCheckResolveIpMessage": "Failed to resolve the IP Address for the Configured Proxy Host {0}", "ProxyPasswordHelpText": "You only need to enter a username and password if one is required. Leave them blank otherwise.", @@ -415,7 +414,7 @@ "Search": "Search", "SearchAllIndexers": "Search all indexers", "SearchCapabilities": "Search Capabilities", - "SearchCountIndexers": "Search {0} indexers", + "SearchCountIndexers": "Search {count} indexer(s)", "SearchIndexers": "Search Indexers", "SearchQueries": "Search Queries", "SearchType": "Search Type", @@ -429,7 +428,7 @@ "Seeders": "Seeders", "SelectAll": "Select All", "SelectIndexers": "Select Indexers", - "SelectedCountOfCountReleases": "Selected {0} of {1} releases", + "SelectedCountOfCountReleases": "Selected {selectedCount} of {itemCount} releases", "SemiPrivate": "Semi-Private", "SendAnonymousUsageData": "Send Anonymous Usage Data", "SetTags": "Set Tags", @@ -487,9 +486,9 @@ "TestAllApps": "Test All Apps", "TestAllClients": "Test All Clients", "TestAllIndexers": "Test All Indexers", - "TheLatestVersionIsAlreadyInstalled": "The latest version of {0} is already installed", + "TheLatestVersionIsAlreadyInstalled": "The latest version of {appName} is already installed", "Theme": "Theme", - "ThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by {0}", + "ThemeHelpText": "Change Application UI Theme, 'Auto' Theme will use your OS Theme to set Light or Dark mode. Inspired by {inspiredBy}.", "Time": "Time", "Title": "Title", "Today": "Today",