Use named tokens in frontend translate function

pull/1827/head
Bogdan 10 months ago
parent 5dbb59dfaa
commit 31261f66ad

@ -288,7 +288,7 @@ class AddIndexerModalContent extends Component {
<div className={styles.available}>
{
isPopulated ?
translate('CountIndexersAvailable', [filteredIndexers.length]) :
translate('CountIndexersAvailable', { count: filteredIndexers.length }) :
null
}
</div>

@ -37,7 +37,7 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
</ModalHeader>
<ModalBody>
{translate('AreYouSureYouWantToDeleteIndexer', [name])}
{translate('AreYouSureYouWantToDeleteIndexer', { name })}
</ModalBody>
<ModalFooter>

@ -48,7 +48,9 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
<ModalBody>
<div className={styles.message}>
{translate('DeleteSelectedIndexersMessageText', [indexers.length])}
{translate('DeleteSelectedIndexersMessageText', {
count: indexers.length,
})}
</div>
<ul>

@ -257,7 +257,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('CountIndexersSelected', [selectedCount])}
{translate('CountIndexersSelected', { count: selectedCount })}
</div>
<div>

@ -165,7 +165,7 @@ function IndexerIndexSelectFooter() {
</div>
<div className={styles.selected}>
{translate('CountIndexersSelected', [selectedCount])}
{translate('CountIndexersSelected', { count: selectedCount })}
</div>
<EditIndexerModal

@ -191,10 +191,10 @@ class SearchFooter extends Component {
icon = icons.SEARCH;
}
let footerLabel = searchIndexerIds.length === 0 ? translate('SearchAllIndexers') : translate('SearchCountIndexers', [searchIndexerIds.length]);
let footerLabel = searchIndexerIds.length === 0 ? translate('SearchAllIndexers') : translate('SearchCountIndexers', { count: searchIndexerIds.length });
if (isPopulated) {
footerLabel = selectedCount === 0 ? translate('FoundCountReleases', [itemCount]) : translate('SelectedCountOfCountReleases', [selectedCount, itemCount]);
footerLabel = selectedCount === 0 ? translate('FoundCountReleases', { itemCount }) : translate('SelectedCountOfCountReleases', { selectedCount, itemCount });
}
return (

@ -127,7 +127,7 @@ class Application extends Component {
isOpen={this.state.isDeleteApplicationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteApplication')}
message={translate('DeleteApplicationMessageText', [name])}
message={translate('DeleteApplicationMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteApplication}
onCancel={this.onDeleteApplicationModalClose}

@ -114,7 +114,7 @@ function ManageApplicationsEditModalContent(
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('CountApplicationsSelected', [selectedCount])}
{translate('CountApplicationsSelected', { count: selectedCount })}
</div>
<div>

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

@ -88,7 +88,7 @@ class Category extends Component {
message={
<div>
<div>
{translate('AreYouSureYouWantToDeleteCategory', [name])}
{translate('AreYouSureYouWantToDeleteCategory')}
</div>
</div>
}

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

@ -129,7 +129,7 @@ function ManageDownloadClientsEditModalContent(
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('CountDownloadClientsSelected', [selectedCount])}
{translate('CountDownloadClientsSelected', { count: selectedCount })}
</div>
<div>

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

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

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

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

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

@ -147,7 +147,7 @@ class UISettings extends Component {
<FormInputGroup
type={inputTypes.SELECT}
name="theme"
helpText={translate('ThemeHelpText', ['Theme.Park'])}
helpText={translate('ThemeHelpText', { inspiredBy: 'Theme.Park' })}
values={themeOptions}
onChange={onInputChange}
{...settings.theme}

@ -138,7 +138,7 @@ class BackupRow extends Component {
isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER}
title={translate('DeleteBackup')}
message={translate('DeleteBackupMessageText', [name])}
message={translate('DeleteBackupMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeletePress}
onCancel={this.onConfirmDeleteModalClose}

@ -114,7 +114,7 @@ class Updates extends Component {
/>
<div className={styles.message}>
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
{translate('TheLatestVersionIsAlreadyInstalled', { appName: 'Prowlarr' })}
</div>
{

@ -25,14 +25,19 @@ export async function fetchTranslations(): Promise<boolean> {
export default function translate(
key: string,
args?: (string | number | boolean)[]
tokens?: Record<string, string | number | boolean>
) {
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;

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

Loading…
Cancel
Save