diff --git a/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx b/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx index 932d7ba7fa9..eeb224d1063 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx @@ -51,7 +51,7 @@ const AdvancedOptions: FunctionComponent = ({ if (!identifier) { if (privateUsername?.length > 0) { - application.alerts.alert(c('Error').t`Unable to compute private username.`).catch(console.error) + application.alerts.alert(c('B1.Account.SignIn.Error').t`Unable to compute private username.`).catch(console.error) } return } @@ -113,7 +113,7 @@ const AdvancedOptions: FunctionComponent = ({ onClick={toggleShowAdvanced} >
- {c('Action').t`Advanced options`} + {c('B1.Account.SignIn.Action').t`Advanced options`}
@@ -125,7 +125,7 @@ const AdvancedOptions: FunctionComponent = ({
= ({ href="https://standardnotes.com/help/80" target="_blank" rel="noopener noreferrer" - title={c('Action').t`Learn more`} + title={c('B1.Account.SignIn.Action').t`Learn more`} > @@ -146,7 +146,7 @@ const AdvancedOptions: FunctionComponent = ({ className={{ container: 'mb-2' }} left={[]} type="text" - placeholder={c('Label').t`Username`} + placeholder={c('B1.Account.SignIn.Label').t`Username`} value={privateUsername} onChange={handlePrivateUsernameNameChange} disabled={disabled || isRecoveryCodes} @@ -160,7 +160,7 @@ const AdvancedOptions: FunctionComponent = ({
= ({ href="https://standardnotes.com/help/security" target="_blank" rel="noopener noreferrer" - title={c('Action').t`Learn more`} + title={c('B1.Account.SignIn.Action').t`Learn more`} > @@ -180,7 +180,7 @@ const AdvancedOptions: FunctionComponent = ({
= ({ className={{ container: 'mb-2' }} left={[]} type="text" - placeholder={c('Label').t`Recovery code`} + placeholder={c('B1.Account.SignIn.Label').t`Recovery code`} value={recoveryCodes} onChange={handleRecoveryCodesChange} disabled={disabled} diff --git a/packages/web/src/javascripts/Components/AccountMenu/ConfirmNoMergeDialog.tsx b/packages/web/src/javascripts/Components/AccountMenu/ConfirmNoMergeDialog.tsx index e1719a591d8..36e6dfa50b0 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/ConfirmNoMergeDialog.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/ConfirmNoMergeDialog.tsx @@ -13,7 +13,7 @@ const ConfirmNoMergeDialog: FunctionComponent = ({ onClose, onConfirm }) return (
- {c('Title').t`Delete local data?`} + {c('B1.Account.SignIn.Title').t`Delete local data?`} @@ -21,18 +21,18 @@ const ConfirmNoMergeDialog: FunctionComponent = ({ onClose, onConfirm })

- {c('Info') + {c('B1.Account.SignIn.Info') .t`You have chosen not to merge your local data. If you proceed, your local notes and tags will be permanently deleted and replaced with data from your account. This action cannot be undone.`}

- {c('Info').t`Are you sure you want to continue without merging?`} + {c('B1.Account.SignIn.Info').t`Are you sure you want to continue without merging?`}

- +
diff --git a/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx b/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx index 1124f461794..f61ba67f9b6 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/ConfirmPassword.tsx @@ -163,25 +163,15 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor checkIfCaptchaRequiredAndRegister() }, [checkIfCaptchaRequiredAndRegister]) + const noPasswordResetOption = ( + {c('B1.Account.SignIn.Info').t`Standard Notes does not have a password reset option`} + ) + const confirmPasswordForm = ( <>
- { - // translator: Full sentence: "Because your notes are encrypted using your password, Standard Notes does not have a password reset option. If you forget your password, you will permanently lose access to your data." - c('Info').t`Because your notes are encrypted using your password,` - }{' '} - { - - { - // translator: Full sentence: "Because your notes are encrypted using your password, Standard Notes does not have a password reset option. If you forget your password, you will permanently lose access to your data." - c('Info').t`Standard Notes does not have a password reset option` - } - - } - { - // translator: Full sentence: "Because your notes are encrypted using your password, Standard Notes does not have a password reset option. If you forget your password, you will permanently lose access to your data." - c('Info').t`. If you forget your password, you will permanently lose access to your data.` - } + {c('B1.Account.SignIn.Info') + .jt`Because your notes are encrypted using your password, ${noPasswordResetOption}. If you forget your password, you will permanently lose access to your data.`}
{!isRegistering && ( @@ -191,7 +181,7 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor left={[]} onChange={handlePasswordChange} onKeyDown={handleKeyDown} - placeholder={c('Label').t`Confirm password`} + placeholder={c('B1.Account.SignIn.Label').t`Confirm password`} ref={passwordInputRef} value={confirmPassword} /> @@ -201,13 +191,13 @@ const ConfirmPassword: FunctionComponent = ({ setMenuPane, email, passwor primary fullWidth className="mb-3 mt-1" - label={isRegistering ? c('Action').t`Creating account...` : c('Action').t`Create account & sign in`} + label={isRegistering ? c('B1.Account.SignIn.Action').t`Creating account...` : c('B1.Account.SignIn.Action').t`Create account & sign in`} onClick={handleConfirmFormSubmit} disabled={isRegistering} /> = ({ setMenuPane, email, passwor
- {captchaURL ? c('Title').t`Human verification` : c('Title').t`Confirm password`} + {captchaURL ? c('B1.Account.SignIn.Title').t`Human verification` : c('B1.Account.SignIn.Title').t`Confirm password`}
{captchaURL ?
{captchaIframe}
: confirmPasswordForm} diff --git a/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx b/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx index 3cee72526ef..85f1f76c19f 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx @@ -102,12 +102,12 @@ const CreateAccount: FunctionComponent = ({ setMenuPane, email, setEmail,
-
{c('Title').t`Create account`}
+
{c('B1.Account.SignIn.Title').t`Create account`}
= ({ setMenuPane, email, setEmail, left={[]} onChange={handleEmailChange} onKeyDown={handleKeyDown} - placeholder={c('Label').t`Email`} + placeholder={c('B1.Account.SignIn.Label').t`Email`} ref={emailInputRef} type="email" value={email} @@ -127,13 +127,13 @@ const CreateAccount: FunctionComponent = ({ setMenuPane, email, setEmail, left={[]} onChange={handlePasswordChange} onKeyDown={handleKeyDown} - placeholder={c('Label').t`Password`} + placeholder={c('B1.Account.SignIn.Label').t`Password`} ref={passwordInputRef} value={password} /> @@ -38,10 +38,10 @@ const ConfirmDeleteAccountModal = ({ application }: Props) => {
diff --git a/packages/web/src/javascripts/Components/ConfirmSignoutModal/ConfirmSignoutModal.tsx b/packages/web/src/javascripts/Components/ConfirmSignoutModal/ConfirmSignoutModal.tsx index fadc31d4682..14d1a1ba68c 100644 --- a/packages/web/src/javascripts/Components/ConfirmSignoutModal/ConfirmSignoutModal.tsx +++ b/packages/web/src/javascripts/Components/ConfirmSignoutModal/ConfirmSignoutModal.tsx @@ -38,7 +38,7 @@ const ConfirmSignoutModal: FunctionComponent = ({ application, applicatio return (
- {c('Title').t`Sign out workspace?`} + {c('B1.Account.Session.Title').t`Sign out workspace?`} @@ -50,8 +50,8 @@ const ConfirmSignoutModal: FunctionComponent = ({ application, applicatio <>

- {c('Label').t`Note:`} - {c('Info') + {c('B1.Account.Session.Label').t`Note:`} + {c('B1.Account.Session.Info') .t`Because you have other workspaces signed in, this sign out may leave logs and other metadata of your session on this device. For a more robust sign out that performs a hard clear of all app-related data, use the "Sign out all workspaces" option under "Switch workspace".`}

@@ -66,7 +66,7 @@ const ConfirmSignoutModal: FunctionComponent = ({ application, applicatio

- {c('Info') + {c('B1.Account.Session.Info') .t`Local backups are enabled for this workspace. Review your backup files manually to decide what to keep.`}

@@ -84,10 +84,10 @@ const ConfirmSignoutModal: FunctionComponent = ({ application, applicatio
diff --git a/packages/web/src/javascripts/Components/ImportModal/ImportModal.tsx b/packages/web/src/javascripts/Components/ImportModal/ImportModal.tsx index f79460ef851..809ed88e4c7 100644 --- a/packages/web/src/javascripts/Components/ImportModal/ImportModal.tsx +++ b/packages/web/src/javascripts/Components/ImportModal/ImportModal.tsx @@ -42,7 +42,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM const modalActions: ModalAction[] = useMemo( () => [ { - label: c('Action').t`Import`, + label: c('B1.Account.ImportExport.Action').t`Import`, type: 'primary', onClick: parseAndImport, hidden: !isReadyToImport, @@ -50,7 +50,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM disabled: !isReadyToImport || (addImportsToTag && !shouldCreateTag && !existingTagForImports), }, { - label: importSuccessOrError ? c('Action').t`Close` : c('Action').t`Cancel`, + label: importSuccessOrError ? c('B1.Account.ImportExport.Action').t`Close` : c('B1.Account.ImportExport.Action').t`Cancel`, type: 'cancel', onClick: close, mobileSlot: 'left', @@ -78,7 +78,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM return ( - +
{!files.length && } {files.length > 0 && ( @@ -102,7 +102,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM }} small > - {c('Action').t`Add files`} + {c('B1.Account.ImportExport.Action').t`Add files`} )} @@ -111,7 +111,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM {files.length > 0 && (
- {c('Label').t`Add all imported notes to tag`} + {c('B1.Account.ImportExport.Label').t`Add all imported notes to tag`} {addImportsToTag && ( <> @@ -125,7 +125,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM setShouldCreateTag(true) }} /> - {c('Label').t`Create new tag`} + {c('B1.Account.ImportExport.Label').t`Create new tag`}
@@ -139,7 +139,7 @@ const ImportModal = ({ importModalController }: { importModalController: ImportM setShouldCreateTag(false) }} /> - {c('Label').t`Add to existing tag`} + {c('B1.Account.ImportExport.Label').t`Add to existing tag`} {existingTagForImports && ( setExistingTagForImports(tag as SNTag)} - placeholder={c('Placeholder').t`Select tag to add imported notes to...`} + placeholder={c('B1.Account.ImportExport.Placeholder').t`Select tag to add imported notes to...`} contentTypes={[ContentType.TYPES.Tag]} />
diff --git a/packages/web/src/javascripts/Components/ImportModal/ImportModalController.ts b/packages/web/src/javascripts/Components/ImportModal/ImportModalController.ts index 2096d0864c4..5224ebb1ab6 100644 --- a/packages/web/src/javascripts/Components/ImportModal/ImportModalController.ts +++ b/packages/web/src/javascripts/Components/ImportModal/ImportModalController.ts @@ -13,6 +13,7 @@ import { action, makeObservable, observable, runInAction } from 'mobx' import { NavigationController } from '../../Controllers/Navigation/NavigationController' import { LinkingController } from '@/Controllers/LinkingController' import { AbstractViewController } from '@/Controllers/Abstract/AbstractViewController' +import { c } from 'ttag' type ImportModalFileCommon = { id: string @@ -175,7 +176,7 @@ export class ImportModalController extends AbstractViewController { this.updateFile({ ...file, status: 'error', - error: error instanceof Error ? error : new Error('Could not import file'), + error: error instanceof Error ? error : new Error(c('B1.Account.ImportExport.Error').t`Could not import file`), }) console.error(error) } @@ -185,10 +186,11 @@ export class ImportModalController extends AbstractViewController { } if (this.addImportsToTag) { const currentDate = new Date() + const importedOnDate = currentDate.toLocaleString() let importTag: SNTag | undefined if (this.shouldCreateTag) { const importTagItem = this.items.createTemplateItem(ContentType.TYPES.Tag, { - title: `Imported on ${currentDate.toLocaleString()}`, + title: c('B1.Account.ImportExport.Label').t`Imported on ${importedOnDate}`, expanded: false, iconString: '', references: importedItems diff --git a/packages/web/src/javascripts/Components/ImportModal/ImportModalFileItem.tsx b/packages/web/src/javascripts/Components/ImportModal/ImportModalFileItem.tsx index cb2f956ee48..c048458ff6b 100644 --- a/packages/web/src/javascripts/Components/ImportModal/ImportModalFileItem.tsx +++ b/packages/web/src/javascripts/Components/ImportModal/ImportModalFileItem.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite' import { useCallback, useEffect, useState } from 'react' import Icon from '../Icon/Icon' import { Disclosure, DisclosureContent, DisclosureProvider } from '@ariakit/react' -import { c, msgid, ngettext } from 'ttag' +import { c, msgid } from 'ttag' const NoteImportTypeColors: Record = { evernote: 'bg-[#14cc45] text-[#000]', @@ -27,6 +27,29 @@ const NoteImportTypeIcons: Record = { super: 'file-doc', } +const formatTranslatedList = (parts: string[]) => { + return new Intl.ListFormat(undefined, { style: 'long', type: 'conjunction' }).format(parts) +} + +const formatImportSuccessMessage = (notes: number, tags: number, files: number) => { + const parts: string[] = [] + + if (notes > 0) { + parts.push(c('B1.Account.ImportExport.Info').ngettext(msgid`${notes} note`, `${notes} notes`, notes)) + } + if (tags > 0) { + parts.push(c('B1.Account.ImportExport.Info').ngettext(msgid`${tags} tag`, `${tags} tags`, tags)) + } + if (files > 0) { + parts.push(c('B1.Account.ImportExport.Info').ngettext(msgid`${files} file`, `${files} files`, files)) + } + + const list = formatTranslatedList(parts) + const total = notes + tags + files + + return c('B1.Account.ImportExport.Info').ngettext(msgid`Imported ${list}`, `Imported ${list}`, total) +} + const countSuccessfulItemsByGroup = (successful: ConversionResult['successful']) => { let notes = 0 let tags = 0 @@ -56,7 +79,7 @@ const ImportErroredAccordion = ({ errored }: { errored: ConversionResult['errore
- {ngettext( + {c('B1.Account.ImportExport.Error').ngettext( msgid`Could not import ${count} item (click for details)`, `Could not import ${count} items (click for details)`, count, @@ -83,17 +106,12 @@ const ImportFinishedStatus = ({ file }: { file: ImportModalFile }) => { const { notes, tags, files } = countSuccessfulItemsByGroup(file.successful) - const notesStatus = notes > 0 ? ngettext(msgid`${notes} note`, `${notes} notes`, notes) : '' - const tagsStatus = tags > 0 ? ngettext(msgid`${tags} tag`, `${tags} tags`, tags) : '' - const filesStatus = files > 0 ? ngettext(msgid`${files} file`, `${files} files`, files) : '' - const status = [notesStatus, tagsStatus, filesStatus].filter(Boolean).join(', ') - return ( <> {file.successful.length > 0 && (
- {c('Info').t`${status} imported`} + {formatImportSuccessMessage(notes, tags, files)}
)} {file.errored.length > 0 && } @@ -162,16 +180,16 @@ const ImportModalFileItem = ({
{file.file.name}
{isDetectingService ? ( -
{c('Status').t`Detecting service...`}
+
{c('B1.Account.ImportExport.Status').t`Detecting service...`}
) : (
- {file.status === 'pending' && file.service && c('Status').t`Ready to import`} + {file.status === 'pending' && file.service && c('B1.Account.ImportExport.Status').t`Ready to import`} {file.status === 'pending' && !file.service && - c('Status').t`Could not auto-detect service. Please select manually.`} - {file.status === 'parsing' && c('Status').t`Parsing...`} - {file.status === 'importing' && c('Status').t`Importing...`} - {file.status === 'uploading-files' && c('Status').t`Uploading and embedding files...`} + c('B1.Account.ImportExport.Status').t`Could not auto-detect service. Please select manually.`} + {file.status === 'parsing' && c('B1.Account.ImportExport.Status').t`Parsing...`} + {file.status === 'importing' && c('B1.Account.ImportExport.Status').t`Importing...`} + {file.status === 'uploading-files' && c('B1.Account.ImportExport.Status').t`Uploading and embedding files...`} {file.status === 'error' && file.error.message}
@@ -196,16 +214,16 @@ const ImportModalFileItem = ({ className="mr-2 rounded border border-border bg-default px-2 py-1 text-sm" defaultValue={file.service ? file.service : undefined} > - - - - - - - + + + + + + + )} -
{c('Info').t`or import from:`}
+
{c('B1.Account.ImportExport.Info').t`or import from:`}
diff --git a/packages/web/src/javascripts/Components/PasswordWizard/FinishStep.tsx b/packages/web/src/javascripts/Components/PasswordWizard/FinishStep.tsx index ed3028c5be9..f56b6eabe77 100644 --- a/packages/web/src/javascripts/Components/PasswordWizard/FinishStep.tsx +++ b/packages/web/src/javascripts/Components/PasswordWizard/FinishStep.tsx @@ -9,9 +9,9 @@ export const FinishStep = () => {
-
{c('Info').t`Your password has been successfully changed.`}
+
{c('B1.Account.Password.Info').t`Your password has been successfully changed.`}

- {c('Info') + {c('B1.Account.Password.Info') .t`Ensure you are running the latest version of Standard Notes on all platforms for maximum compatibility.`}

diff --git a/packages/web/src/javascripts/Components/PasswordWizard/PasswordStep.tsx b/packages/web/src/javascripts/Components/PasswordWizard/PasswordStep.tsx index 9cf1fdf92d4..c3111a40c70 100644 --- a/packages/web/src/javascripts/Components/PasswordWizard/PasswordStep.tsx +++ b/packages/web/src/javascripts/Components/PasswordWizard/PasswordStep.tsx @@ -34,7 +34,7 @@ export const PasswordStep = ({
{ const currentPassword = this.state.formData.currentPassword const newPass = this.state.formData.newPassword if (!currentPassword || currentPassword.length === 0) { - this.application.alerts.alert(c('Error').t`Please enter your current password.`).catch(console.error) + this.application.alerts.alert(c('B1.Account.Password.Error').t`Please enter your current password.`).catch(console.error) return false } if (!newPass || newPass.length === 0) { - this.application.alerts.alert(c('Error').t`Please enter a new password.`).catch(console.error) + this.application.alerts.alert(c('B1.Account.Password.Error').t`Please enter a new password.`).catch(console.error) return false } if (newPass !== this.state.formData.newPasswordConfirmation) { this.application.alerts - .alert(c('Error').t`Your new password does not match its confirmation.`) + .alert(c('B1.Account.Password.Error').t`Your new password does not match its confirmation.`) .catch(console.error) this.setFormDataState({ status: undefined, @@ -159,7 +159,7 @@ class PasswordWizard extends AbstractComponent { if (!this.application.sessions.getUser()?.email) { this.application.alerts - .alert(c('Error').t`We don't have your email stored. Please sign out then log back in to fix this issue.`) + .alert(c('B1.Account.Password.Error').t`We don't have your email stored. Please sign out then log back in to fix this issue.`) .catch(console.error) this.setFormDataState({ status: undefined, @@ -170,7 +170,7 @@ class PasswordWizard extends AbstractComponent { const success = await this.application.validateAccountPassword(this.state.formData.currentPassword as string) if (!success) { this.application.alerts - .alert(c('Error').t`The current password you entered is not correct. Please try again.`) + .alert(c('B1.Account.Password.Error').t`The current password you entered is not correct. Please try again.`) .catch(console.error) } return success @@ -185,7 +185,7 @@ class PasswordWizard extends AbstractComponent { }) await this.setFormDataState({ - status: c('Status').t`Processing encryption keys…`, + status: c('B1.Account.Password.Status').t`Processing encryption keys…`, }) const newPassword = this.state.formData.newPassword @@ -202,13 +202,13 @@ class PasswordWizard extends AbstractComponent { if (!success) { this.setFormDataState({ - status: c('Status').t`Unable to process your password. Please try again.`, + status: c('B1.Account.Password.Status').t`Unable to process your password. Please try again.`, }).catch(console.error) } else { this.setState({ formData: { ...this.state.formData, - status: c('Status').t`Successfully changed password.`, + status: c('B1.Account.Password.Status').t`Successfully changed password.`, }, }) } @@ -218,7 +218,7 @@ class PasswordWizard extends AbstractComponent { dismiss = () => { if (this.state.processing) { this.application.alerts - .alert(c('Error').t`Cannot close window until pending tasks are complete.`) + .alert(c('B1.Account.Password.Error').t`Cannot close window until pending tasks are complete.`) .catch(console.error) } else { this.props.dismissModal() @@ -276,12 +276,12 @@ class PasswordWizard extends AbstractComponent { continueLabel(): string { switch (this.state.continueTitle) { case ContinueTitle.Generating: - return c('Action').t`Generating Keys...` + return c('B1.Account.Password.Action').t`Generating Keys...` case ContinueTitle.Finish: - return c('Action').t`Finish` + return c('B1.Account.Password.Action').t`Finish` case ContinueTitle.Default: default: - return c('Action').t`Continue` + return c('B1.Account.Password.Action').t`Continue` } } @@ -289,11 +289,11 @@ class PasswordWizard extends AbstractComponent { return (
-

{c('Status').t`Checking for data conflicts...`}

+

{c('B1.Account.Password.Status').t`Checking for data conflicts...`}

) } @@ -89,7 +89,7 @@ export const PreprocessingStep = ({ return (

- {c('Info') + {c('B1.Account.Password.Info') .t`You have pending vault invites. Changing your password will delete these invites. It is recommended you accept or decline these invites before changing your password. If you choose to continue, these invites will be deleted.`}

diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/Authentication.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/Authentication.tsx index aed09bbb614..4b7c8de0468 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/Authentication.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/Authentication.tsx @@ -28,7 +28,7 @@ const Authentication: FunctionComponent = ({ application }) => { const loginLink = ( ) @@ -37,13 +37,13 @@ const Authentication: FunctionComponent = ({ application }) => {
- {c('Title').t`You're not signed in`} + {c('B1.Account.SignIn.Title').t`You're not signed in`}
- {c('Info') + {c('B1.Account.SignIn.Info') .t`Sign in to sync your notes and preferences across all your devices and enable end-to-end encryption.`}
-
diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx index feb613cc748..2a68ee98fcf 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx @@ -36,7 +36,7 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = const validateCurrentPassword = useCallback(async () => { if (!currentPassword || currentPassword.length === 0) { - applicationAlertService.alert(c('Error').t`Please enter your current password.`).catch(console.error) + applicationAlertService.alert(c('B1.Account.Session.Error').t`Please enter your current password.`).catch(console.error) return false } @@ -44,7 +44,7 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = const success = await application.validateAccountPassword(currentPassword) if (!success) { applicationAlertService - .alert(c('Error').t`The current password you entered is not correct. Please try again.`) + .alert(c('B1.Account.Session.Error').t`The current password you entered is not correct. Please try again.`) .catch(console.error) return false @@ -75,7 +75,7 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = const dismiss = useCallback(() => { if (lockContinue) { applicationAlertService - .alert(c('Error').t`Cannot close window until pending tasks are complete.`) + .alert(c('B1.Account.Session.Error').t`Cannot close window until pending tasks are complete.`) .catch(console.error) } else { onCloseDialog() @@ -119,7 +119,7 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = const handleDialogClose = useCallback(() => { if (lockContinue) { applicationAlertService - .alert(c('Error').t`Cannot close window until pending tasks are complete.`) + .alert(c('B1.Account.Session.Error').t`Cannot close window until pending tasks are complete.`) .catch(console.error) } else { onCloseDialog() @@ -129,19 +129,19 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = const submitButtonLabel = useMemo(() => { switch (submitButtonTitle) { case SubmitButtonTitles.GeneratingKeys: - return c('Action').t`Generating Keys...` + return c('B1.Account.Session.Action').t`Generating Keys...` case SubmitButtonTitles.Finish: - return c('Action').t`Finish` + return c('B1.Account.Session.Action').t`Finish` case SubmitButtonTitles.Default: default: - return c('Action').t`Continue` + return c('B1.Account.Session.Action').t`Continue` } }, [submitButtonTitle]) const modalActions = useMemo( (): ModalAction[] => [ { - label: c('Action').t`Cancel`, + label: c('B1.Account.Session.Action').t`Cancel`, onClick: handleDialogClose, type: 'cancel', mobileSlot: 'left', @@ -157,7 +157,7 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = ) return ( - +
{currentStep === Steps.InitialStep && ( diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmailForm.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmailForm.tsx index aa46b10b97a..40d3f1ffc00 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmailForm.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmailForm.tsx @@ -15,7 +15,7 @@ const ChangeEmailForm: FunctionComponent = ({ setNewEmail, setCurrentPass
= ({ setNewEmail, setCurrentPass
{ return (
-
{c('Info').t`Your email has been successfully changed.`}
+
{c('B1.Account.Session.Info').t`Your email has been successfully changed.`}

- {c('Info') + {c('B1.Account.Session.Info') .t`Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.`}

diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ClearSessionDataView.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ClearSessionDataView.tsx index 7e1fa9e6871..99ba847cc54 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ClearSessionDataView.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ClearSessionDataView.tsx @@ -13,12 +13,12 @@ const ClearSessionDataView: FunctionComponent = () => { return ( - {c('Title').t`Clear workspace`} - {c('Info') + {c('B1.Account.Session.Title').t`Clear workspace`} + {c('B1.Account.Session.Info') .t`Remove all data related to the current workspace from the application.`}
- {c('Subtitle').t`This workspace`} - {c('Info').t`Remove all data related to the current workspace from the application.`} + {c('B1.Account.Session.Subtitle').t`This workspace`} + {c('B1.Account.Session.Info').t`Remove all data related to the current workspace from the application.`}
diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/Encryption.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/Encryption.tsx index f46064eca14..65ebd833422 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/Encryption.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/Encryption.tsx @@ -15,10 +15,10 @@ const Encryption: FunctionComponent = () => { const isEncryptionEnabled = app.isEncryptionAvailable() const encryptionStatusString = hasUser - ? STRING_E2E_ENABLED + ? STRING_E2E_ENABLED() : hasPasscode - ? STRING_LOCAL_ENC_ENABLED - : STRING_ENC_NOT_ENABLED + ? STRING_LOCAL_ENC_ENABLED() + : STRING_ENC_NOT_ENABLED() return ( diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx index 84d70508439..c2b6c1555cc 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Security/PasscodeLock.tsx @@ -68,10 +68,10 @@ const PasscodeLock = ({ application }: Props) => { const encryptionEnabled = hasUser || hasPasscode const encryptionStatusString = hasUser - ? STRING_E2E_ENABLED + ? STRING_E2E_ENABLED() : hasPasscode - ? STRING_LOCAL_ENC_ENABLED - : STRING_ENC_NOT_ENABLED + ? STRING_LOCAL_ENC_ENABLED() + : STRING_ENC_NOT_ENABLED() setEncryptionStatusString(encryptionStatusString) setIsEncryptionEnabled(encryptionEnabled) diff --git a/packages/web/src/javascripts/Components/PurchaseFlow/Panes/CreateAccount.tsx b/packages/web/src/javascripts/Components/PurchaseFlow/Panes/CreateAccount.tsx index 47698cf2d7f..59a9737251a 100644 --- a/packages/web/src/javascripts/Components/PurchaseFlow/Panes/CreateAccount.tsx +++ b/packages/web/src/javascripts/Components/PurchaseFlow/Panes/CreateAccount.tsx @@ -151,19 +151,19 @@ const CreateAccount: FunctionComponent = ({ application }) => { className={`min-w-auto md:min-w-90 ${isEmailInvalid ? 'mb-2' : 'mb-4'}`} id="purchase-sign-in-email" type="email" - label={c('Label').t`Email`} + label={c('B1.Account.SignIn.Label').t`Email`} value={email} onChange={handleEmailChange} ref={emailInputRef} disabled={isCreatingAccount} isInvalid={isEmailInvalid} /> - {isEmailInvalid ?
{c('Error').t`Please provide a valid email.`}
: null} + {isEmailInvalid ?
{c('B1.Account.SignIn.Error').t`Please provide a valid email.`}
: null} = ({ application }) => { className={`min-w-auto md:min-w-90 ${isPasswordNotMatching ? 'mb-2' : 'mb-4'}`} id="create-account-confirm" type="password" - label={c('Label').t`Repeat password`} + label={c('B1.Account.SignIn.Label').t`Repeat password`} value={confirmPassword} onChange={handleConfirmPasswordChange} ref={confirmPasswordInputRef} @@ -181,7 +181,7 @@ const CreateAccount: FunctionComponent = ({ application }) => { isInvalid={isPasswordNotMatching} /> {isPasswordNotMatching ? ( -
{c('Error').t`Passwords don't match. Please try again.`}
+
{c('B1.Account.SignIn.Error').t`Passwords don't match. Please try again.`}
) : null}
@@ -200,11 +200,11 @@ const CreateAccount: FunctionComponent = ({ application }) => {
{ // translator: Full sentence: "Create your free account" -

{c('Title').t`Create your free account`}

+

{c('B1.Account.SignIn.Title').t`Create your free account`}

} { // translator: Full sentence: "Create your free account to continue to Standard Notes." -
{c('Info').t`to continue to Standard Notes.`}
+
{c('B1.Account.SignIn.Info').t`to continue to Standard Notes.`}
} {captchaURL ? captchaIframe : CreateAccountForm}
@@ -216,7 +216,7 @@ const CreateAccount: FunctionComponent = ({ application }) => { > { // translator: "Instead" here refers to "instead of creating an account" - c('Action').t`Sign in instead` + c('B1.Account.SignIn.Action').t`Sign in instead` } {!application.isNativeIOS() && ( @@ -225,14 +225,14 @@ const CreateAccount: FunctionComponent = ({ application }) => { disabled={isCreatingAccount} className="flex cursor-pointer items-start border-0 bg-default p-0 font-medium text-info hover:underline" > - {c('Action').t`Subscribe without account`} + {c('B1.Account.SignIn.Action').t`Subscribe without account`} )}
)} - ))} + ) + })} )} @@ -175,17 +186,20 @@ const SessionsModalContent: FunctionComponent<{ {confirmRevokingSessionUuid && (
- {SessionStrings.RevokeTitle} + {c('B1.Account.Session.Title').t`Revoke this session?`}
-

{SessionStrings.RevokeText}

+

+ {c('B1.Account.Session.Info') + .t`The associated app will be signed out and all data removed from the device when it is next launched. You can sign back in on that device at any time.`} +

diff --git a/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx b/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx index ea88dcc53aa..cb479665e1d 100644 --- a/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx +++ b/packages/web/src/javascripts/Components/U2FAuthIframe/U2FAuthIframe.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import Button from '../Button/Button' import { startAuthentication } from '@simplewebauthn/browser' import { log, LoggingDomain } from '@/Logging' +import { c } from 'ttag' const CHROME_CLIPPER_EXTENSION_ORIGIN = 'chrome-extension://heapafmadojoodklnkhjanbinemaagok' const FIREFOX_CLIPPER_EXTENSION_ORIGIN = 'moz-extension://2a461925-d1b1-4ed3-99a6-91fe7633cc2c' @@ -65,7 +66,7 @@ const U2FAuthIframe = () => { try { if (!username || !source || !parentOrigin) { - throw new Error('No username provided') + throw new Error(c('B1.Account.SignIn.Error').t`No username provided`) } const response = await fetch(`${apiHost}/v1/authenticators/generate-authentication-options`, { @@ -80,10 +81,10 @@ const U2FAuthIframe = () => { const jsonResponse = await response.json() if (!jsonResponse.data || !jsonResponse.data.options) { - throw new Error('No options returned from server') + throw new Error(c('B1.Account.SignIn.Error').t`No options returned from server`) } - setInfo('Waiting for security key...') + setInfo(c('B1.Account.SignIn.Status').t`Waiting for security key...`) const assertionResponse = await startAuthentication(jsonResponse.data.options) @@ -94,7 +95,7 @@ const U2FAuthIframe = () => { parentOrigin, ) - setInfo('Authentication successful!') + setInfo(c('B1.Account.SignIn.Info').t`Authentication successful!`) } catch (error) { if (!error) { return @@ -107,9 +108,10 @@ const U2FAuthIframe = () => { return (
- Insert your hardware security key, then press the button below to authenticate. + {c('B1.Account.SignIn.Info') + .t`Insert your hardware security key, then press the button below to authenticate.`}
- +
{info}
{error}
diff --git a/packages/web/src/javascripts/Constants/Strings.ts b/packages/web/src/javascripts/Constants/Strings.ts index 4e8cb3c50fa..fdd3394dbd4 100644 --- a/packages/web/src/javascripts/Constants/Strings.ts +++ b/packages/web/src/javascripts/Constants/Strings.ts @@ -1,15 +1,14 @@ import { escapeHtmlString, Platform, SNApplication } from '@standardnotes/snjs' import { getPlatform, isDesktopApplication } from '../Utils' -import { c } from 'ttag' +import { c, msgid } from 'ttag' /** @generic */ -export const STRING_SESSION_EXPIRED = () => - c('Error').t`Your session has expired. New changes will not be pulled in. Please sign in to refresh your session.` export const STRING_DEFAULT_FILE_ERROR = () => c('Error') .t`Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.com/filesafe.` export const STRING_GENERIC_SYNC_ERROR = () => - c('Error').t`There was an error syncing. Please try again. If all else fails, try signing out and signing back in.` + c('B1.Account.Session.Error') + .t`There was an error syncing. Please try again. If all else fails, try signing out and signing back in.` export function StringSyncException(data: unknown) { return `There was an error while trying to save your items. Please contact support and share this message: ${JSON.stringify( data, @@ -47,50 +46,52 @@ export function StringDeleteNote(title: string, permanently: boolean) { : `Are you sure you want to move ${escapedTitle} to the trash?` } export function StringEmptyTrash(count: number) { - return `Are you sure you want to permanently delete ${count} note(s)?` + return c('Confirmation').ngettext( + msgid`Are you sure you want to permanently delete ${count} note?`, + `Are you sure you want to permanently delete ${count} notes?`, + count, + ) } /** @account */ -export const STRING_ACCOUNT_MENU_UNCHECK_MERGE = - 'Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?' export const STRING_SIGN_OUT_CONFIRMATION = () => - c('Info') + c('B1.Account.Session.Info') .t`This action will remove this workspace and its related data from this device. Your synced data will not be affected.` -export const STRING_ERROR_DECRYPTING_IMPORT = - 'There was an error decrypting your items. Make sure the password you entered is correct and try again.' -export const STRING_E2E_ENABLED = - 'End-to-end encryption is enabled. Your data is encrypted on your device first, then synced to your private cloud.' -export const STRING_LOCAL_ENC_ENABLED = - 'Encryption is enabled. Your data is encrypted using your passcode before it is saved to your device storage.' -export const STRING_ENC_NOT_ENABLED = - 'Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption.' -export const STRING_IMPORT_SUCCESS = () => c('Info').t`Your data has been successfully imported.` +export const STRING_E2E_ENABLED = () => + c('B1.Account.ImportExport.Info') + .t`End-to-end encryption is enabled. Your data is encrypted on your device first, then synced to your private cloud.` +export const STRING_LOCAL_ENC_ENABLED = () => + c('B1.Account.ImportExport.Info') + .t`Encryption is enabled. Your data is encrypted using your passcode before it is saved to your device storage.` +export const STRING_ENC_NOT_ENABLED = () => + c('B1.Account.ImportExport.Info') + .t`Encryption is not enabled. Sign in, register, or add a passcode lock to enable encryption.` +export const STRING_IMPORT_SUCCESS = () => + c('B1.Account.ImportExport.Info').t`Your data has been successfully imported.` export const STRING_REMOVE_PASSCODE_CONFIRMATION = 'Are you sure you want to remove your application passcode?' export const STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM = ' This will remove encryption from your local data.' export const STRING_NON_MATCHING_PASSCODES = 'The two passcodes you entered do not match. Please try again.' export const STRING_NON_MATCHING_PASSWORDS = () => - c('Error').t`The two passwords you entered do not match. Please try again.` -export const STRING_GENERATING_LOGIN_KEYS = 'Generating Login Keys...' -export const STRING_GENERATING_REGISTER_KEYS = 'Generating Account Keys...' + c('B1.Account.SignIn.Error').t`The two passwords you entered do not match. Please try again.` export const STRING_INVALID_IMPORT_FILE = () => - c('Error').t`Unable to open file. Ensure it is a proper JSON file and try again.` + c('B1.Account.ImportExport.Error').t`Unable to open file. Ensure it is a proper JSON file and try again.` export const STRING_IMPORTING_ZIP_FILE = () => - c('Error') + c('B1.Account.ImportExport.Error') .t`The file you selected is not a valid backup file. Please extract the contents of the zip file, then upload the contained .txt file.` export function StringImportError(errorCount: number) { - return c('Info') - .t`Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.` + return c('B1.Account.ImportExport.Info').ngettext( + msgid`Import complete. ${errorCount} item was not imported because there was an error decrypting it. Make sure the password is correct and try again.`, + `Import complete. ${errorCount} items were not imported because there was an error decrypting them. Make sure the password is correct and try again.`, + errorCount, + ) } export const STRING_UNSUPPORTED_BACKUP_FILE_VERSION = () => - c('Error') + c('B1.Account.ImportExport.Error') .t`This backup file was created using an unsupported version of the application and cannot be imported here. Please update your application and try again.` /** @password_change */ -export const STRING_FAILED_PASSWORD_CHANGE = - 'There was an error re-encrypting your items. Your password was changed, but not all your items were properly re-encrypted and synced. You should try syncing again. If all else fails, you should restore your notes from backup.' - export const STRING_CONFIRM_APP_QUIT_DURING_UPGRADE = () => - c('Info') + c('B1.Account.Password.Info') .t`The encryption upgrade is in progress. You may lose data if you quit the app. Are you sure you want to quit?` export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE = @@ -99,20 +100,20 @@ export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE = export const STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL = 'A passcode removal is in progress. You may lose data if you quit the app. ' + 'Are you sure you want to quit?' -export const STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE = () => c('Title').t`Encryption upgrade available` +export const STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE = () => c('B1.Account.Password.Title').t`Encryption upgrade available` export const STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT = () => - c('Info') + c('B1.Account.Password.Info') .t`Encryption version 004 is available. This version strengthens the encryption algorithms your account and local storage use. To learn more about this upgrade, visit our Security Upgrade page.` -export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = () => c('Action').t`Upgrade` +export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = () => c('B1.Account.Password.Action').t`Upgrade` export const STRING_REMOVE_OFFLINE_KEY_CONFIRMATION = 'This will delete the previously saved offline key.' export const STRING_DELETE_ACCOUNT_CONFIRMATION = () => - c('Info') + c('B1.Account.Session.Info') .t`Are you sure you want to permanently delete your account? You will be asked to confirm your account password in the next step. If you have an active paid subscription, cancel the subscription first. Otherwise, if you'd like to keep the subscription, you can re-register with the same email after deletion, and your subscription will be linked back up with your account.` export const STRING_FAILED_TO_UPDATE_USER_SETTING = () => - c('Error').t`There was an error while trying to update your settings. Please try again.` + c('B1.Account.Session.Error').t`There was an error while trying to update your settings. Please try again.` export const Strings = { protectingNoteWithoutProtectionSources: