Use named tokens in frontend translate function

This commit is contained in:
Bogdan 2023-08-14 00:58:22 +03:00
parent 6d99de4fe0
commit 3ddeaaefe2
20 changed files with 52 additions and 49 deletions

View file

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

View file

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

View file

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

View file

@ -113,7 +113,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}

View file

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

View file

@ -286,9 +286,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}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -227,7 +227,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}

View file

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

View file

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

View file

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

View file

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

View file

@ -25,18 +25,19 @@ export async function fetchTranslations(): Promise<boolean> {
export default function translate(
key: string,
args?: (string | number | boolean)[]
tokens?: Record<string, string | number | boolean>
) {
if (!(key in translations)) {
console.debug(key);
}
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;

View file

@ -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",
"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}'",
"CountDownloadClientsSelected": "{0} download client(s) selected",
"CountImportListsSelected": "{0} import list(s) selected",
"CountIndexersSelected": "{0} indexer(s) selected",
"CountAuthorsSelected": "{selectedCount} author(s) selected",
"CountDownloadClientsSelected": "{selectedCount} download client(s) selected",
"CountImportListsSelected": "{selectedCount} import list(s) selected",
"CountIndexersSelected": "{selectedCount} indexer(s) selected",
"Country": "Country",
"CreateEmptyAuthorFolders": "Create empty author folders",
"CreateEmptyAuthorFoldersHelpText": "Create missing author folders during disk scan",
@ -202,48 +203,48 @@
"DelayingDownloadUntilInterp": "Delaying download until {0} at {1}",
"Delete": "Delete",
"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",
"DeleteBookFileMessageText": "Are you sure you want to delete {0}?",
"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",
"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",
"DeleteDelayProfileMessageText": "Are you sure you want to delete this delay profile?",
"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",
"DeleteEmptyFoldersHelpText": "Delete empty author folders during disk scan and when book files are deleted",
"DeleteFilesHelpText": "Delete the book files and author folder",
"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",
"DeleteImportListExclusion": "Delete 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",
"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",
"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",
"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",
"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",
"DeleteReleaseProfileMessageText": "Are you sure you want to delete this Release Profile?",
"DeleteRemotePathMapping": "Delete Remote Path Mapping",
"DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?",
"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",
"DeleteSelectedBookFilesMessageText": "Are you sure you want to delete the selected book files?",
"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)",
"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)",
"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}'?",
"DestinationPath": "Destination Path",
@ -995,6 +996,7 @@
"WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder",
"WatchRootFoldersForFileChanges": "Watch Root Folders for file changes",
"WeekColumnHeader": "Week Column Header",
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
"WriteAudioTags": "Tag Audio Files with Metadata",
"WriteAudioTagsScrub": "Scrub Existing Tags",
"WriteAudioTagsScrubHelp": "Remove existing tags from files, leaving only those added by Readarr.",