- {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",