Use named tokens in frontend translate function

pull/2829/head
Bogdan 2 years ago
parent 6d99de4fe0
commit 3ddeaaefe2

@ -143,7 +143,7 @@ class BookshelfFooter extends Component {
<div> <div>
<div className={styles.label}> <div className={styles.label}>
{selectedCount} Author(s) Selected {translate('CountAuthorsSelected', { selectedCount })}
</div> </div>
<SpinnerButton <SpinnerButton

@ -152,7 +152,7 @@ class CustomFormat extends Component {
isOpen={this.state.isDeleteCustomFormatModalOpen} isOpen={this.state.isDeleteCustomFormatModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteCustomFormat')} title={translate('DeleteCustomFormat')}
message={translate('DeleteCustomFormatMessageText', [name])} message={translate('DeleteCustomFormatMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
isSpinning={isDeleting} isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteCustomFormat} onConfirm={this.onConfirmDeleteCustomFormat}

@ -115,7 +115,7 @@ class Specification extends Component {
isOpen={this.state.isDeleteSpecificationModalOpen} isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteCondition')} title={translate('DeleteCondition')}
message={translate('DeleteConditionMessageText', [name])} message={translate('DeleteConditionMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification} onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose} onCancel={this.onDeleteSpecificationModalClose}

@ -113,7 +113,7 @@ class DownloadClient extends Component {
isOpen={this.state.isDeleteDownloadClientModalOpen} isOpen={this.state.isDeleteDownloadClientModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteDownloadClient')} title={translate('DeleteDownloadClient')}
message={translate('DeleteDownloadClientMessageText', [name])} message={translate('DeleteDownloadClientMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteDownloadClient} onConfirm={this.onConfirmDeleteDownloadClient}
onCancel={this.onDeleteDownloadClientModalClose} onCancel={this.onDeleteDownloadClientModalClose}

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

@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteSelectedDownloadClients')} title={translate('DeleteSelectedDownloadClients')}
message={translate('DeleteSelectedDownloadClientsMessageText', [ message={translate('DeleteSelectedDownloadClientsMessageText', {
selectedIds.length, count: selectedIds.length,
])} })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose} onCancel={onDeleteModalClose}

@ -107,7 +107,7 @@ class ImportList extends Component {
isOpen={this.state.isDeleteImportListModalOpen} isOpen={this.state.isDeleteImportListModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteImportList')} title={translate('DeleteImportList')}
message={translate('DeleteImportListMessageText', [name])} message={translate('DeleteImportListMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteImportList} onConfirm={this.onConfirmDeleteImportList}
onCancel={this.onDeleteImportListModalClose} onCancel={this.onDeleteImportListModalClose}

@ -184,7 +184,7 @@ function ManageImportListsEditModalContent(
<ModalFooter className={styles.modalFooter}> <ModalFooter className={styles.modalFooter}>
<div className={styles.selected}> <div className={styles.selected}>
{translate('CountImportListsSelected', [selectedCount])} {translate('CountImportListsSelected', { selectedCount })}
</div> </div>
<div> <div>

@ -283,9 +283,9 @@ function ManageImportListsModalContent(
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteSelectedImportLists')} title={translate('DeleteSelectedImportLists')}
message={translate('DeleteSelectedImportListsMessageText', [ message={translate('DeleteSelectedImportListsMessageText', {
selectedIds.length, count: selectedIds.length,
])} })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose} onCancel={onDeleteModalClose}

@ -152,7 +152,7 @@ class Indexer extends Component {
isOpen={this.state.isDeleteIndexerModalOpen} isOpen={this.state.isDeleteIndexerModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteIndexer')} title={translate('DeleteIndexer')}
message={translate('DeleteIndexerMessageText', [name])} message={translate('DeleteIndexerMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteIndexer} onConfirm={this.onConfirmDeleteIndexer}
onCancel={this.onDeleteIndexerModalClose} onCancel={this.onDeleteIndexerModalClose}

@ -178,7 +178,7 @@ function ManageIndexersEditModalContent(
<ModalFooter className={styles.modalFooter}> <ModalFooter className={styles.modalFooter}>
<div className={styles.selected}> <div className={styles.selected}>
{translate('CountIndexersSelected', [selectedCount])} {translate('CountIndexersSelected', { selectedCount })}
</div> </div>
<div> <div>

@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteSelectedIndexers')} title={translate('DeleteSelectedIndexers')}
message={translate('DeleteSelectedIndexersMessageText', [ message={translate('DeleteSelectedIndexersMessageText', {
selectedIds.length, count: selectedIds.length,
])} })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete} onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose} onCancel={onDeleteModalClose}

@ -95,7 +95,7 @@ class RootFolder extends Component {
isOpen={this.state.isDeleteRootFolderModalOpen} isOpen={this.state.isDeleteRootFolderModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteRootFolder')} title={translate('DeleteRootFolder')}
message={translate('DeleteRootFolderMessageText', [name])} message={translate('DeleteRootFolderMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteRootFolder} onConfirm={this.onConfirmDeleteRootFolder}
onCancel={this.onDeleteRootFolderModalClose} onCancel={this.onDeleteRootFolderModalClose}

@ -227,7 +227,7 @@ class Notification extends Component {
isOpen={this.state.isDeleteNotificationModalOpen} isOpen={this.state.isDeleteNotificationModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteNotification')} title={translate('DeleteNotification')}
message={translate('DeleteNotificationMessageText', [name])} message={translate('DeleteNotificationMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteNotification} onConfirm={this.onConfirmDeleteNotification}
onCancel={this.onDeleteNotificationModalClose} onCancel={this.onDeleteNotificationModalClose}

@ -144,7 +144,7 @@ class MetadataProfile extends Component {
isOpen={this.state.isDeleteMetadataProfileModalOpen} isOpen={this.state.isDeleteMetadataProfileModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteMetadataProfile')} title={translate('DeleteMetadataProfile')}
message={translate('DeleteMetadataProfileMessageText', [name])} message={translate('DeleteMetadataProfileMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
isSpinning={isDeleting} isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteMetadataProfile} onConfirm={this.onConfirmDeleteMetadataProfile}

@ -162,7 +162,7 @@ class QualityProfile extends Component {
isOpen={this.state.isDeleteQualityProfileModalOpen} isOpen={this.state.isDeleteQualityProfileModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteQualityProfile')} title={translate('DeleteQualityProfile')}
message={translate('DeleteQualityProfileMessageText', [name])} message={translate('DeleteQualityProfileMessageText', { name })}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
isSpinning={isDeleting} isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteQualityProfile} onConfirm={this.onConfirmDeleteQualityProfile}

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

@ -146,7 +146,7 @@ class RestoreBackupModalContent extends Component {
<ModalBody> <ModalBody>
{ {
!!id && `Would you like to restore the backup '${name}'?` !!id && translate('WouldYouLikeToRestoreBackup', { name })
} }
{ {

@ -25,18 +25,19 @@ export async function fetchTranslations(): Promise<boolean> {
export default function translate( export default function translate(
key: string, key: string,
args?: (string | number | boolean)[] tokens?: Record<string, string | number | boolean>
) { ) {
if (!(key in translations)) {
console.debug(key);
}
const translation = translations[key] || key; const translation = translations[key] || key;
if (args) { if (tokens) {
return translation.replace(/\{(\d+)\}/g, (match, index) => { // Fallback to the old behaviour for translations not yet updated to use named tokens
return String(args[index]) ?? match; Object.values(tokens).forEach((value, index) => {
tokens[index] = value;
}); });
return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) =>
String(tokens[tokenMatch] ?? match)
);
} }
return translation; return translation;

@ -161,9 +161,10 @@
"CopyUsingHardlinksHelpText": "Hardlinks allow Readarr to import seeding torrents to the the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume", "CopyUsingHardlinksHelpText": "Hardlinks allow Readarr to import seeding torrents to the the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume",
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Readarr's rename function as a work around.", "CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Readarr's rename function as a work around.",
"CouldntFindAnyResultsForTerm": "Couldn't find any results for '{0}'", "CouldntFindAnyResultsForTerm": "Couldn't find any results for '{0}'",
"CountDownloadClientsSelected": "{0} download client(s) selected", "CountAuthorsSelected": "{selectedCount} author(s) selected",
"CountImportListsSelected": "{0} import list(s) selected", "CountDownloadClientsSelected": "{selectedCount} download client(s) selected",
"CountIndexersSelected": "{0} indexer(s) selected", "CountImportListsSelected": "{selectedCount} import list(s) selected",
"CountIndexersSelected": "{selectedCount} indexer(s) selected",
"Country": "Country", "Country": "Country",
"CreateEmptyAuthorFolders": "Create empty author folders", "CreateEmptyAuthorFolders": "Create empty author folders",
"CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan", "CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan",
@ -202,48 +203,48 @@
"DelayingDownloadUntilInterp": "Delaying download until {0} at {1}", "DelayingDownloadUntilInterp": "Delaying download until {0} at {1}",
"Delete": "Delete", "Delete": "Delete",
"DeleteBackup": "Delete Backup", "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}'?",
"DeleteBookFile": "Delete Book File", "DeleteBookFile": "Delete Book File",
"DeleteBookFileMessageText": "Are you sure you want to delete {0}?", "DeleteBookFileMessageText": "Are you sure you want to delete {0}?",
"DeleteCondition": "Delete Condition", "DeleteCondition": "Delete Condition",
"DeleteConditionMessageText": "Are you sure you want to delete condition '{0}'?", "DeleteConditionMessageText": "Are you sure you want to delete condition '{name}'?",
"DeleteCustomFormat": "Delete Custom Format", "DeleteCustomFormat": "Delete Custom Format",
"DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{0}'?", "DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{name}'?",
"DeleteDelayProfile": "Delete Delay Profile", "DeleteDelayProfile": "Delete Delay Profile",
"DeleteDelayProfileMessageText": "Are you sure you want to delete this delay profile?", "DeleteDelayProfileMessageText": "Are you sure you want to delete this delay profile?",
"DeleteDownloadClient": "Delete Download Client", "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}'?",
"DeleteEmptyFolders": "Delete empty folders", "DeleteEmptyFolders": "Delete empty folders",
"DeleteEmptyFoldersHelpText": "Delete empty author folders during disk scan and when book files are deleted", "DeleteEmptyFoldersHelpText": "Delete empty author folders during disk scan and when book files are deleted",
"DeleteFilesHelpText": "Delete the book files and author folder", "DeleteFilesHelpText": "Delete the book files and author folder",
"DeleteFormat": "Delete Format", "DeleteFormat": "Delete Format",
"DeleteFormatMessageText": "Are you sure you want to delete format tag {0} ?", "DeleteFormatMessageText": "Are you sure you want to delete format tag '{0}'?",
"DeleteImportList": "Delete Import List", "DeleteImportList": "Delete Import List",
"DeleteImportListExclusion": "Delete Import List Exclusion", "DeleteImportListExclusion": "Delete Import List Exclusion",
"DeleteImportListExclusionMessageText": "Are you sure you want to delete this import list exclusion?", "DeleteImportListExclusionMessageText": "Are you sure you want to delete this import list exclusion?",
"DeleteImportListMessageText": "Are you sure you want to delete the list '{0}'?", "DeleteImportListMessageText": "Are you sure you want to delete the list '{name}'?",
"DeleteIndexer": "Delete Indexer", "DeleteIndexer": "Delete Indexer",
"DeleteIndexerMessageText": "Are you sure you want to delete the indexer '{0}'?", "DeleteIndexerMessageText": "Are you sure you want to delete the indexer '{name}'?",
"DeleteMetadataProfile": "Delete Metadata Profile", "DeleteMetadataProfile": "Delete Metadata Profile",
"DeleteMetadataProfileMessageText": "Are you sure you want to delete the metadata profile '{0}'?", "DeleteMetadataProfileMessageText": "Are you sure you want to delete the metadata profile '{name}'?",
"DeleteNotification": "Delete Notification", "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}'?",
"DeleteQualityProfile": "Delete Quality Profile", "DeleteQualityProfile": "Delete Quality Profile",
"DeleteQualityProfileMessageText": "Are you sure you want to delete the quality profile '{0}'?", "DeleteQualityProfileMessageText": "Are you sure you want to delete the quality profile '{name}'?",
"DeleteReleaseProfile": "Delete Release Profile", "DeleteReleaseProfile": "Delete Release Profile",
"DeleteReleaseProfileMessageText": "Are you sure you want to delete this Release Profile?", "DeleteReleaseProfileMessageText": "Are you sure you want to delete this Release Profile?",
"DeleteRemotePathMapping": "Delete Remote Path Mapping", "DeleteRemotePathMapping": "Delete Remote Path Mapping",
"DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?", "DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?",
"DeleteRootFolder": "Delete Root Folder", "DeleteRootFolder": "Delete Root Folder",
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{0}'?", "DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{name}'?",
"DeleteSelectedBookFiles": "Delete Selected Book Files", "DeleteSelectedBookFiles": "Delete Selected Book Files",
"DeleteSelectedBookFilesMessageText": "Are you sure you want to delete the selected book files?", "DeleteSelectedBookFilesMessageText": "Are you sure you want to delete the selected book files?",
"DeleteSelectedDownloadClients": "Delete Download Client(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)?",
"DeleteSelectedImportLists": "Delete Import List(s)", "DeleteSelectedImportLists": "Delete Import List(s)",
"DeleteSelectedImportListsMessageText": "Are you sure you want to delete {0} selected import list(s)?", "DeleteSelectedImportListsMessageText": "Are you sure you want to delete {count} selected import list(s)?",
"DeleteSelectedIndexers": "Delete Indexer(s)", "DeleteSelectedIndexers": "Delete Indexer(s)",
"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", "DeleteTag": "Delete Tag",
"DeleteTagMessageText": "Are you sure you want to delete the tag '{0}'?", "DeleteTagMessageText": "Are you sure you want to delete the tag '{0}'?",
"DestinationPath": "Destination Path", "DestinationPath": "Destination Path",
@ -995,6 +996,7 @@
"WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder", "WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder",
"WatchRootFoldersForFileChanges": "Watch Root Folders for file changes", "WatchRootFoldersForFileChanges": "Watch Root Folders for file changes",
"WeekColumnHeader": "Week Column Header", "WeekColumnHeader": "Week Column Header",
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
"WriteAudioTags": "Tag Audio Files with Metadata", "WriteAudioTags": "Tag Audio Files with Metadata",
"WriteAudioTagsScrub": "Scrub Existing Tags", "WriteAudioTagsScrub": "Scrub Existing Tags",
"WriteAudioTagsScrubHelp": "Remove existing tags from files, leaving only those added by Readarr.", "WriteAudioTagsScrubHelp": "Remove existing tags from files, leaving only those added by Readarr.",

Loading…
Cancel
Save