Use named tokens in frontend translate function

pull/4013/head
Bogdan 9 months ago
parent 98ae377aff
commit f83e2ad73c

@ -146,7 +146,7 @@ class AlbumStudioFooter extends Component {
<div>
<div className={styles.label}>
{translate('CountArtistsSelected', [selectedCount])}
{translate('CountArtistsSelected', { selectedCount })}
</div>
<SpinnerButton

@ -289,7 +289,7 @@ class ArtistEditorFooter extends Component {
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<ArtistEditorFooterLabel
label={translate('SelectedCountArtistsSelectedInterp', [selectedCount])}
label={translate('SelectedCountArtistsSelectedInterp', { selectedCount })}
isSaving={false}
/>

@ -120,7 +120,7 @@ function ArtistIndexPosterInfo(props) {
if (albumCount === 0) {
albums = translate('NoAlbums');
} else if (albumCount > 1) {
albums = translate('CountAlbums', [albumCount]);
albums = translate('CountAlbums', { albumCount });
}
return (

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -146,7 +146,7 @@ class RestoreBackupModalContent extends Component {
<ModalBody>
{
!!id && translate('WouldYouLikeToRestoreBackup', [name])
!!id && translate('WouldYouLikeToRestoreBackup', { name })
}
{

@ -1,36 +0,0 @@
import createAjaxRequest from 'Utilities/createAjaxRequest';
function getTranslations() {
return createAjaxRequest({
global: false,
dataType: 'json',
url: '/localization'
}).request;
}
let translations = {};
export function fetchTranslations() {
return new Promise(async(resolve) => {
try {
const data = await getTranslations();
translations = data.strings;
resolve(true);
} catch (error) {
resolve(false);
}
});
}
export default function translate(key, args = []) {
const translation = translations[key] || key;
if (args) {
return translation.replace(/\{(\d+)\}/g, (match, index) => {
return args[index];
});
}
return translation;
}

@ -0,0 +1,44 @@
import createAjaxRequest from 'Utilities/createAjaxRequest';
function getTranslations() {
return createAjaxRequest({
global: false,
dataType: 'json',
url: '/localization',
}).request;
}
let translations: Record<string, string> = {};
export async function fetchTranslations(): Promise<boolean> {
return new Promise(async (resolve) => {
try {
const data = await getTranslations();
translations = data.strings;
resolve(true);
} catch (error) {
resolve(false);
}
});
}
export default function translate(
key: string,
tokens?: Record<string, string | number | boolean>
) {
const translation = translations[key] || key;
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;
}

@ -165,11 +165,11 @@
"CopyUsingHardlinksHelpText": "Hardlinks allow Lidarr 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 Lidarr's rename function as a work around.",
"CouldntFindAnyResultsForTerm": "Couldn't find any results for '{0}'",
"CountAlbums": "{0} albums",
"CountArtistsSelected": "{0} artist(s) selected",
"CountDownloadClientsSelected": "{0} download client(s) selected",
"CountImportListsSelected": "{0} import list(s) selected",
"CountIndexersSelected": "{0} indexer(s) selected",
"CountAlbums": "{albumCount} albums",
"CountArtistsSelected": "{selectedCount} artist(s) selected",
"CountDownloadClientsSelected": "{selectedCount} download client(s) selected",
"CountImportListsSelected": "{selectedCount} import list(s) selected",
"CountIndexersSelected": "{selectedCount} indexer(s) selected",
"Country": "Country",
"CreateEmptyArtistFolders": "Create empty artist folders",
"CreateEmptyArtistFoldersHelpText": "Create missing artist folders during disk scan",
@ -203,45 +203,45 @@
"Delete": "Delete",
"DeleteArtist": "Delete Selected Artist",
"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}'?",
"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 artist and album folders during disk scan and when track files are deleted",
"DeleteFilesHelpText": "Delete the track files and artist 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 '{name}'?",
"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 ReleaseProfile",
"DeleteReleaseProfileMessageText": "Are you sure you want to delete this releaseProfile?",
"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}'?",
"DeleteSelected": "Delete Selected",
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {0} selected download client(s)?",
"DeleteSelectedDownloadClients": "Delete 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)?",
"DeleteSelectedTrackFiles": "Delete Selected Track Files",
"DeleteSelectedTrackFilesMessageText": "Are you sure you want to delete the selected track files?",
"DeleteTag": "Delete Tag",
@ -846,7 +846,7 @@
"SelectQuality": "Select Quality",
"SelectReleaseGroup": "Select Release Group",
"SelectTracks": "Select Tracks",
"SelectedCountArtistsSelectedInterp": "{0} Artist(s) Selected",
"SelectedCountArtistsSelectedInterp": "{selectedCount} Artist(s) Selected",
"SendAnonymousUsageData": "Send Anonymous Usage Data",
"SetPermissions": "Set Permissions",
"SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?",
@ -1047,7 +1047,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 {0} ?",
"WouldYouLikeToRestoreBackup": "Would you like to restore the backup '{name}'?",
"WriteAudioTagsHelpTextWarning": "Selecting 'All files' will alter existing files when they are imported.",
"WriteMetadataTags": "Write Metadata Tags",
"WriteMetadataToAudioFiles": "Write Metadata to Audio Files",

Loading…
Cancel
Save