mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-21 14:03:29 -07:00
Use named tokens in frontend translate function
This commit is contained in:
parent
98ae377aff
commit
f83e2ad73c
23 changed files with 92 additions and 84 deletions
|
@ -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;
|
||||
}
|
44
frontend/src/Utilities/String/translate.ts
Normal file
44
frontend/src/Utilities/String/translate.ts
Normal file
|
@ -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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue