A task list is what is left to do, so closing something has to take it off the plan. But "what did I get done" is a real question and the answer was nowhere: once a task was closed it left the interface entirely. They come back in one list at the bottom rather than back among the open ones in their sections. A closed task has stopped being work and started being a record, and putting the record in the middle of the plan makes the plan longer without making it say more. Off by default, and deliberately: a list that opens full of things already done is a list you have to read past before you can use it. In localStorage rather than on the account, like the sidebar's width and unlike a folded group. It is a way of *looking*, not a statement about the work — somebody reviewing their week wants it on for ten minutes, and a setting that followed them to every device is one they would have to turn off twice. `loadTasks` asks for closed tasks when the setting is on, rather than each caller deciding. A view added later cannot then forget and quietly ignore it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
587 lines
27 KiB
JavaScript
587 lines
27 KiB
JavaScript
/**
|
||
* English, translated from `da.js`.
|
||
*
|
||
* Danish is the source: it is the language the app was written in, and the one
|
||
* whose phrasing was argued over. A key missing here falls through to Danish
|
||
* rather than to the key itself — a Danish sentence in an English screen can be
|
||
* read, and `settings.notifications.title` cannot.
|
||
*
|
||
* Dates are not here. They go through `tag()`, which returns `en-GB` for this
|
||
* locale: the app is day-month everywhere, weeks start on Monday, and an American
|
||
* date format inside a European layout reads as a bug.
|
||
*/
|
||
export const en = {
|
||
// --- the shell ---------------------------------------------------------------
|
||
'nav.today': 'Today',
|
||
'nav.upcoming': 'Upcoming',
|
||
'nav.delegated': 'Waiting on others',
|
||
'nav.inbox': 'Inbox',
|
||
'nav.settings': 'Settings',
|
||
'nav.signOut': 'Sign out',
|
||
'nav.main': 'Main menu',
|
||
'nav.search': 'Search',
|
||
'nav.searchLong': 'Search tasks and projects',
|
||
'nav.noResults': 'Nothing found.',
|
||
'nav.showSidebar': 'Show the sidebar',
|
||
'nav.hideSidebar': 'Hide the sidebar',
|
||
'nav.showMenu': 'Show menu',
|
||
'nav.sidebarWidth': 'Sidebar width',
|
||
'nav.toggleTheme': 'Switch between light and dark',
|
||
'nav.offline': 'Offline',
|
||
'nav.offlineHint': 'Changes from other people are not showing right now',
|
||
'nav.doItToday': 'Do it today',
|
||
|
||
// --- the sidebar's own sections ------------------------------------------------
|
||
'sidebar.projects': 'Projects',
|
||
'sidebar.shared': 'Shared with me',
|
||
'sidebar.filters': 'Filters',
|
||
'sidebar.labels': 'Labels',
|
||
'sidebar.newProject': 'New project',
|
||
'sidebar.newGroup': 'New group',
|
||
'sidebar.projectName': 'Project name',
|
||
'sidebar.groupName': "The group's name",
|
||
'sidebar.noProjects': 'No projects yet.',
|
||
'sidebar.emptyGroup': 'Empty — drag a project up here.',
|
||
'sidebar.rename': 'Rename',
|
||
'sidebar.delete': 'Delete',
|
||
'sidebar.fold': 'Collapse {name}',
|
||
'sidebar.unfold': 'Expand {name}',
|
||
'sidebar.deleteGroup': 'Delete the group "{name}"?',
|
||
'sidebar.deleteGroupOne': 'The project in it stays, and ends up without a group.',
|
||
'sidebar.deleteGroupMany': 'The {n} projects stay, and end up without a group.',
|
||
|
||
// --- a task in a list ----------------------------------------------------------
|
||
'task.add': 'Add',
|
||
'task.new': 'New task',
|
||
'task.placeholder': 'Add a task — try “pay VAT tomorrow at 10 p1 #Company”',
|
||
'task.complete': 'Mark as done',
|
||
'task.reopen': 'Reopen task',
|
||
'task.today': 'Today',
|
||
'task.tomorrow': 'Tomorrow',
|
||
'task.yesterday': 'Yesterday',
|
||
'task.overdue': '{n} days overdue',
|
||
'task.repeats': 'Repeats {rule}',
|
||
'task.assignee': 'Assigned to {name}',
|
||
'task.subtasks': '{done} of {total} sub-tasks are done',
|
||
'task.attachments': '{n} attached',
|
||
'task.priority': 'Priority {n}',
|
||
'task.noPriority': 'No priority',
|
||
|
||
// --- a project group -----------------------------------------------------------
|
||
'group.colorOf': 'Colour of {name}',
|
||
|
||
|
||
// --- settings: the frame -------------------------------------------------------
|
||
'settings.title': 'Settings',
|
||
'settings.tab.account': 'Account',
|
||
'settings.tab.notifications': 'Notifications',
|
||
'settings.tab.integrations': 'Integrations',
|
||
'settings.tab.ai': 'AI',
|
||
'settings.tab.tokens': 'API tokens',
|
||
'settings.tab.users': 'People',
|
||
'settings.tab.history': 'History',
|
||
'settings.tab.errors': 'Errors',
|
||
'settings.tab.data': 'Data and templates',
|
||
|
||
// --- settings: the account -----------------------------------------------------
|
||
'account.profile': 'Profile',
|
||
'account.profileHint':
|
||
'The timezone is not decoration: every date in the app is resolved in it, so “tomorrow at 9” means something else once you change it.',
|
||
'account.name': 'Name',
|
||
'account.email': 'Email',
|
||
'account.emailFixed': 'Cannot be changed here.',
|
||
'account.timezone': 'Timezone',
|
||
'account.parseLanguage': 'Language',
|
||
'account.danish': 'Dansk',
|
||
'account.english': 'English',
|
||
'account.parseHint':
|
||
'Both the interface’s language and the grammar quick add reads a line with — “hver mandag” or “every monday”.',
|
||
'account.save': 'Save',
|
||
'account.saved': 'Saved.',
|
||
'account.appearance': 'Appearance',
|
||
'account.appearanceHint':
|
||
'Kept in this browser. It is a property of the screen you are sitting at — a laptop in the sun and a monitor in a dark room want different answers.',
|
||
'account.password': 'Password',
|
||
'account.passwordHint':
|
||
'Changing it signs every other session out — which is usually the reason to do it.',
|
||
'account.currentPassword': 'Current password',
|
||
'account.newPassword': 'New password',
|
||
'account.changePassword': 'Change password',
|
||
'account.passwordChanged': 'Changed.',
|
||
'account.totp': 'Two-factor',
|
||
'account.totpHint': 'A one-time code from an authenticator app, on top of the password.',
|
||
'account.writeThemDown': 'Write them down now.',
|
||
'account.recoveryHint': 'They are shown this once — the server only has their hash.',
|
||
'account.gotThem': 'I have them',
|
||
'account.passwordRequired':
|
||
'Required for both actions below. Turning two-factor off is exactly what a stranger holding your session would do first.',
|
||
'account.newRecoveryCodes': 'New recovery codes',
|
||
'account.totpOff': 'Turn two-factor off',
|
||
'account.scanHint': 'Scan it in your authenticator app, or type the key in by hand.',
|
||
'account.codeFromApp': 'The code from the app',
|
||
'account.totpProofHint':
|
||
'Two-factor is only on once a code has proved the app really has the key.',
|
||
'account.confirm': 'Confirm',
|
||
'account.cancel': 'Cancel',
|
||
'account.totpOn': 'Turn two-factor on',
|
||
'account.devices': 'Devices',
|
||
'account.devicesHint':
|
||
'Where you are signed in. If you do not recognise one, sign it out and change your password.',
|
||
'account.thisDevice': 'this device',
|
||
'account.signOutDevice': 'Sign out',
|
||
|
||
'account.totpEnabled': 'On.',
|
||
'account.recoveryLeftOne': '{n} recovery code left.',
|
||
'account.recoveryLeftMany': '{n} recovery codes left.',
|
||
|
||
// --- settings: notifications ---------------------------------------------------
|
||
'push.title': 'Notifications on this device',
|
||
'push.hint':
|
||
'Reminders and messages from other people, even with the tab closed. The message is encrypted to your browser on the way — the push service cannot read it.',
|
||
'push.unsupported':
|
||
'This browser does not support it. Safari needs the app added to the home screen; over plain HTTP it only works on localhost.',
|
||
'push.blocked':
|
||
'The browser has blocked notifications for this site. That can only be changed in the browser’s own settings — a page cannot ask again once the answer has been no.',
|
||
'push.on': 'On for this device.',
|
||
'push.turnOff': 'Turn off',
|
||
'push.turnOn': 'Turn on for this device',
|
||
'push.perBrowser': 'Applies only to the browser you are in right now.',
|
||
'push.recent': 'Recent',
|
||
'push.none': 'Nothing yet.',
|
||
'push.open': 'Open',
|
||
'push.markAllRead': 'Mark all as read',
|
||
'push.version': 'Version',
|
||
'push.running': 'Running',
|
||
'push.seeRelease': 'See the release',
|
||
|
||
// --- settings: API tokens ------------------------------------------------------
|
||
'tokens.title': 'API tokens',
|
||
'tokens.hint':
|
||
'What a script, a CalDAV client or an MCP connector signs in with. A token carries exactly your permissions — it can do nothing you cannot.',
|
||
'tokens.copyNow': 'Copy it now.',
|
||
'tokens.onceOnly': 'It is shown this once — the server only has its hash.',
|
||
'tokens.copy': 'Copy',
|
||
'tokens.copied': 'Copied.',
|
||
'tokens.done': 'Done',
|
||
'tokens.mcpBefore': 'Using it as an',
|
||
'tokens.mcpLink': 'MCP connector',
|
||
'tokens.mcpAfter': 'in Claude? Then this is the address the dialog wants:',
|
||
'tokens.copyAddress': 'Copy the address',
|
||
'tokens.name': 'Name',
|
||
'tokens.namePlaceholder': 'e.g. my laptop, or the backup script',
|
||
'tokens.expires': 'Expires',
|
||
'tokens.never': 'Never',
|
||
'tokens.in30': 'In 30 days',
|
||
'tokens.in90': 'In 90 days',
|
||
'tokens.inYear': 'In a year',
|
||
'tokens.expiryHint':
|
||
'A calendar client that has been subscribed for years needs “never”. A script you run once does not.',
|
||
'tokens.create': 'Create a token',
|
||
'tokens.none': 'No tokens yet.',
|
||
'tokens.revoke': 'Revoke',
|
||
|
||
// --- settings: AI --------------------------------------------------------------
|
||
'ai.title': 'AI',
|
||
'ai.hint':
|
||
'Used in two places: splitting a task into sub-tasks, and writing a short overview of what is outstanding. The key stays on your server and is only sent to the provider you pick here.',
|
||
'ai.provider': 'Provider',
|
||
'ai.off': 'Off',
|
||
'ai.model': 'Model',
|
||
'ai.baseURL': 'Address',
|
||
'ai.apiKey': 'API key',
|
||
'ai.save': 'Save',
|
||
'ai.saved': 'Saved.',
|
||
'ai.weekly': 'Weekly overview',
|
||
'ai.weeklyHint': 'A short note on what looks most important right now.',
|
||
|
||
'push.unreadOne': '{n} unread.',
|
||
'push.unreadMany': '{n} unread.',
|
||
|
||
'ai.keyStored': 'A key is stored — leave this blank',
|
||
'ai.keyBlankHint':
|
||
'The field is blank on purpose. The key is never sent back to the browser — a settings page that refills a password field is one that will one day leak a key into a screenshot.',
|
||
'ai.keyNewHint': 'Stored on the server and never shown again.',
|
||
'tokens.lastUsed': 'last used {when}',
|
||
'tokens.neverUsed': 'never used',
|
||
'tokens.expired': 'expired {when}',
|
||
'tokens.expiresOn': 'expires {when}',
|
||
'tokens.revokeNamed': 'Revoke {name}',
|
||
|
||
// --- settings: people ----------------------------------------------------------
|
||
'users.invite': 'Invite somebody',
|
||
'users.inviteHint':
|
||
'There is no open registration. You send a link and the person picks their own name and password — so there is never a password two people know.',
|
||
'users.emailAddress': 'Email address',
|
||
'users.emailPlaceholder': 'name@example.com',
|
||
'users.mustBeEmail': 'Must be an email address.',
|
||
'users.sendInvite': 'Send invitation',
|
||
'users.inviteMade': 'The invitation is ready. Send the link to them:',
|
||
'users.pending': 'Awaiting a reply',
|
||
'users.pendingHint':
|
||
'Invitations sent and not used yet. Only the link’s hash is stored, so it cannot be shown again — if it went to the wrong person, revoke it.',
|
||
'users.revoke': 'Revoke',
|
||
'users.accounts': 'Accounts',
|
||
'users.accountsHint': 'Everybody on this instance.',
|
||
'users.admin': 'administrator',
|
||
'users.you': 'you',
|
||
'users.makeAdmin': 'Make administrator',
|
||
'users.removeAdmin': 'Remove administrator',
|
||
'users.delete': 'Delete',
|
||
'users.deleteQuestion': 'Delete {name} <{email}> for good?',
|
||
'users.deleteTakes': 'That deletes {projects} and the {tasks} in them.',
|
||
'users.deleteLeaves': '{tasks} in other people’s projects stay, without an author.',
|
||
'users.deleteFinal': 'It cannot be undone. There is no trash for accounts.',
|
||
'users.revokeQuestion': 'Revoke the invitation to {email}? The link stops working.',
|
||
'users.projectOne': 'project',
|
||
'users.projectMany': 'projects',
|
||
'users.taskOne': 'task',
|
||
'users.taskMany': 'tasks',
|
||
'users.never': 'never seen',
|
||
|
||
// --- settings: history and errors -----------------------------------------------
|
||
'history.title': 'History',
|
||
'history.hint':
|
||
'Everything that has been done on this server, across every project — including the ones you are not in. A project’s own history panel shows the same rows one project at a time; this is the question those cannot answer. Rows stay when an account is deleted: what was done stops having a name, not having happened.',
|
||
'history.person': 'Person',
|
||
'history.all': 'All',
|
||
'history.project': 'Project',
|
||
'history.event': 'Event',
|
||
'history.clear': 'Clear',
|
||
'history.more': 'Load more',
|
||
'history.loading': 'Loading…',
|
||
'history.noMatch': 'Nothing matches the filters.',
|
||
'history.nothing': 'Nothing has been recorded yet.',
|
||
'history.deletedAccount': 'A deleted account',
|
||
'errors.title': 'Server errors',
|
||
'errors.hint':
|
||
'Every 500 the API has answered, with what the handler was attempting and the error it hit. Written to the database rather than only to the log — a container’s log starts over on every restart, so the explanation is usually gone by the time anybody looks. Kept for 30 days.',
|
||
'errors.none': 'No errors recorded. That is the right state.',
|
||
|
||
// --- the views -------------------------------------------------------------------
|
||
'view.delegated': 'Waiting on others',
|
||
'view.delegatedEmpty': 'You are not waiting on anybody.',
|
||
'view.labelEmpty': 'No open tasks with this label.',
|
||
'view.filterEmpty': 'Nothing matches right now.',
|
||
'view.filterFailed': 'The filter could not be run.',
|
||
'view.upcoming': 'Upcoming',
|
||
'view.mode': 'View',
|
||
'view.list': 'List',
|
||
'view.week': 'Week',
|
||
'view.month': 'Month',
|
||
'view.board': 'Board',
|
||
'view.calendar': 'Calendar',
|
||
'view.emptySection': 'Empty',
|
||
'view.addSection': '+ Add a section',
|
||
'view.sectionName': 'The section’s name',
|
||
'view.newSection': 'New section',
|
||
'view.prevMonth': 'Previous month',
|
||
'view.nextMonth': 'Next month',
|
||
'view.prevWeek': 'Previous week',
|
||
'view.nextWeek': 'Next week',
|
||
'view.weekNumber': 'week {n}',
|
||
'view.more': '+{n} more',
|
||
|
||
// --- how long ago ----------------------------------------------------------------
|
||
'when.justNow': 'just now',
|
||
'when.minutes': '{n} min. ago',
|
||
'when.hours': '{n} hours ago',
|
||
'when.days': '{n} days ago',
|
||
'when.never': 'never',
|
||
'when.neverSignedIn': 'never signed in',
|
||
|
||
// --- settings: data ---------------------------------------------------------------
|
||
'data.import': 'Import from Todoist',
|
||
'data.csvFile': 'CSV file',
|
||
'data.importing': 'Importing …',
|
||
'data.openProject': 'Open the project',
|
||
'data.trash': 'Trash',
|
||
'data.trashEmpty': 'The trash is empty.',
|
||
'data.restore': 'Restore',
|
||
'data.backups': 'Backups',
|
||
'data.backupNow': 'Take one now',
|
||
'data.backingUp': 'Copying…',
|
||
'data.backupMade': 'Backup written — {size}',
|
||
'data.backupFailed': 'failed',
|
||
'data.download': 'Download',
|
||
'data.noBackups': 'No backup has been taken yet.',
|
||
'data.swept': 'swept away',
|
||
'data.empty': 'empty',
|
||
'data.export': 'Export',
|
||
'data.exportAccount': 'The whole account as JSON',
|
||
'data.exportOne': 'One project',
|
||
'data.pickProject': 'Pick a project',
|
||
'data.csv': 'CSV',
|
||
'data.ics': 'Calendar file',
|
||
'data.templates': 'Templates',
|
||
'data.saveAsTemplate': 'Save a project as a template',
|
||
'data.templateName': 'Name for the template',
|
||
'data.saveTemplate': 'Save as a template',
|
||
'data.noTemplates': 'No templates yet.',
|
||
'data.use': 'Use',
|
||
'data.delete': 'Delete',
|
||
'data.newProjectName': 'Name for the new project',
|
||
'data.startDate': 'Start date',
|
||
'data.startDateHint': 'Day zero for the template’s dates. Blank means today.',
|
||
'data.createProject': 'Create the project',
|
||
|
||
// --- a group's own page ------------------------------------------------------------
|
||
'group.notFound': 'That group does not exist, or it is not yours.',
|
||
'group.failed': 'The group could not be loaded.',
|
||
'group.retry': 'Try again',
|
||
'group.about': 'About this group',
|
||
'group.aboutPlaceholder': 'What kind of work is this?',
|
||
'group.writeAbout': 'Write what kind of work this is.',
|
||
'group.saving': 'Saving…',
|
||
'group.projects': 'Projects',
|
||
'group.projectOne': '1 project',
|
||
'group.projectMany': '{n} projects',
|
||
'group.noProjects':
|
||
'No projects in the group yet. Drag one up here in the sidebar, or pick the group when you create one.',
|
||
'group.documents': 'Documents',
|
||
'group.documentsHint':
|
||
'Files that belong to the whole group rather than to one task in it — the contract, the drawing, the agreement everything else rests on.',
|
||
'group.remove': 'Remove',
|
||
'group.removeNamed': 'Remove {name}',
|
||
'group.removeQuestion': 'Remove "{name}"?',
|
||
'group.addFile': '+ Upload a file',
|
||
'group.uploading': 'Uploading…',
|
||
|
||
// --- keyboard shortcuts --------------------------------------------------------------
|
||
'keys.title': 'Shortcuts',
|
||
|
||
'data.importHint':
|
||
'Pick the CSV export of one project. Priorities are converted, not copied: Todoist writes 4 for what its interface calls P1.',
|
||
'data.trashHint':
|
||
'Deleted projects, with their tasks. They come back exactly as they were — except for tasks you had deleted individually first; there was a reason for those.',
|
||
'data.backupsHint':
|
||
'The database is copied every night and the most recent fourteen are kept. Counted, not dated: a container that has been off for a month must not come back and delete all of them for being too old.',
|
||
'data.backupsAttachments':
|
||
'Attachments are not included. They are content-addressed and only ever added to, so fourteen identical copies would fill the volume they were meant to protect — take the whole of /data if you want everything.',
|
||
'data.restoreHint':
|
||
'Putting a copy back is deliberately not a button here: a database cannot be swapped under a server that is writing to it, and a half-finished swap destroys the very thing it was meant to save. Stop the container, put the file in place of verdande.db in the data directory, delete the -wal and -shm beside it, and start it again.',
|
||
'data.exportHint':
|
||
'Everything, in its raw form. That is the guarantee that you can leave — an export that has been tidied up is an export that has lost something.',
|
||
'data.templatesHint':
|
||
'A project saved as a shape you can use again. The dates in a template are relative, so they are worked out from the start date you pick.',
|
||
|
||
'keys.navigation': 'Navigation',
|
||
'keys.jumpToQuickAdd': 'Jump to the “add a task” field',
|
||
'keys.thisPage': 'This page',
|
||
'keys.inQuickAdd': 'In quick add',
|
||
'keys.intoProject': 'Put the task in a project',
|
||
'keys.setLabel': 'Set a label',
|
||
'keys.priority': 'Priority',
|
||
'keys.date': 'A date — Danish and English',
|
||
'keys.time': 'A time',
|
||
'keys.clear': 'Clear the field',
|
||
|
||
// --- signing in ------------------------------------------------------------------
|
||
'auth.firstAccount': 'Create the first account. It becomes the administrator.',
|
||
'auth.name': 'Name',
|
||
'auth.nameRequired': 'Required',
|
||
'auth.email': 'Email',
|
||
'auth.emailInvalid': 'Must be an email address',
|
||
'auth.password': 'Password',
|
||
'auth.passwordHint': 'At least 10 characters. A sentence is both easier and stronger.',
|
||
'auth.createAccount': 'Create account',
|
||
'auth.totpPrompt': 'Enter the code from your authenticator.',
|
||
'auth.code': 'Code',
|
||
'auth.totpRecovery': 'If you have lost your phone, one of your recovery codes works here.',
|
||
'auth.continue': 'Continue',
|
||
'auth.resetPrompt': 'We will send a link to choose a new password.',
|
||
'auth.sendLink': 'Send the link',
|
||
'auth.back': 'Back',
|
||
'auth.linkBroken': 'That link is incomplete. Open it from the email again, or ask for a new one.',
|
||
'auth.toSignIn': 'To sign in',
|
||
'auth.signOut': 'Sign out',
|
||
'auth.invited': 'You have been invited. Choose a name and a password.',
|
||
'auth.pickNew': 'Choose a new password.',
|
||
'auth.newPassword': 'New password',
|
||
'auth.newPasswordHint': 'At least 10 characters. Every other device is signed out.',
|
||
'auth.savePassword': 'Save the password',
|
||
'auth.passwordChanged': 'The password has been changed. Sign in with the new one.',
|
||
'auth.resetSent':
|
||
'If that address has an account, a link is on its way. Check the spam filter too.',
|
||
'auth.signInPrompt': 'Sign in to continue.',
|
||
'auth.signIn': 'Sign in',
|
||
'auth.forgot': 'Forgotten your password?',
|
||
|
||
// --- a task, opened ---------------------------------------------------------------
|
||
'detail.task': 'Task',
|
||
'detail.close': 'Close',
|
||
'detail.text': 'The task’s text',
|
||
'detail.description': 'Description',
|
||
'detail.descriptionPlaceholder': 'Details, links, whatever has to be remembered',
|
||
'detail.priority': 'Priority',
|
||
'detail.p1': 'P1 — urgent',
|
||
'detail.none': 'None',
|
||
'detail.due': 'Due',
|
||
'detail.labels': 'Labels',
|
||
'detail.labelsPlaceholder': 'separated by commas',
|
||
'detail.project': 'Project',
|
||
'detail.repeats': 'Repeats',
|
||
'detail.monthly': 'Every month',
|
||
'detail.yearly': 'Every year',
|
||
'detail.assignee': 'Assigned to',
|
||
'detail.subtasks': 'Sub-tasks',
|
||
'detail.addSubtask': 'Add a sub-task',
|
||
'detail.newSubtask': 'New sub-task',
|
||
'detail.files': 'Files',
|
||
'detail.delete': 'Delete',
|
||
'detail.reminders': 'Reminders',
|
||
'detail.newReminder': 'New reminder',
|
||
'detail.sent': 'sent',
|
||
'detail.add': 'Add',
|
||
'detail.comments': 'Comments',
|
||
'detail.writeComment': 'Write a comment',
|
||
'detail.newComment': 'New comment',
|
||
'detail.post': 'Post',
|
||
'detail.readAs': 'Read as {what}',
|
||
|
||
'auth.alreadySignedIn': 'You are signed in as {name}. Sign out first, then open the link again.',
|
||
|
||
'detail.reopen': 'Reopen',
|
||
'detail.uploading': 'Uploading …',
|
||
'detail.attachFile': 'Attach a file',
|
||
|
||
// --- settings: integrations --------------------------------------------------------
|
||
'int.gmail': 'Gmail',
|
||
'int.gmailHint':
|
||
'Star an email, or give it a label, and it becomes a task. One way: removing the star does nothing to the task.',
|
||
'int.connectGmail': 'Connect Gmail',
|
||
'int.fromEnv':
|
||
'The client is set in the environment (VERDANDE_GMAIL_CLIENT_ID), so it cannot be changed here. That is deliberate: a value in the Rune’s manifest is a decision reapplied on every restart, and a form that could overwrite it would make the manifest a lie.',
|
||
'int.oauthClient': 'OAuth client at Google',
|
||
'int.registerHint':
|
||
'Register a client in Google Cloud Console → APIs & Services → Credentials → OAuth client ID → Web application, enable the Gmail API, and paste the two values here. Your own account has to be listed as a test user for as long as the app is unverified with Google.',
|
||
'int.redirectURI': 'Authorised redirect URI',
|
||
'int.clientID': 'Client ID',
|
||
'int.clientSecret': 'Client secret',
|
||
'int.secretStored': 'Stored — fill this in only to change it',
|
||
'int.save': 'Save',
|
||
'int.saved': 'Saved.',
|
||
'int.noClient':
|
||
'No OAuth client has been registered with Google yet. An administrator has to do that before this button can be used.',
|
||
'int.connectedAs': 'Connected as',
|
||
'int.unknownAccount': 'an unknown account',
|
||
'int.whatMakesATask': 'What makes a task',
|
||
'int.starred': 'Starred',
|
||
'int.aLabel': 'A particular label',
|
||
'int.both': 'Both',
|
||
'int.gmailLabelName': 'The label’s name in Gmail',
|
||
'int.gmailLabelExample': 'e.g. Tasks',
|
||
'int.fetchNow': 'Fetch now',
|
||
'int.disconnect': 'Disconnect',
|
||
'int.feed': 'Calendar feed',
|
||
'int.feedHint':
|
||
'Subscribe in Apple Calendar, Google or Thunderbird. A calendar client cannot sign in, so the key in the address is the whole password — do not share it.',
|
||
'int.address': 'Address',
|
||
'int.copy': 'Copy',
|
||
'int.newLink': 'New link',
|
||
'int.newLinkHint':
|
||
'A new link breaks every existing subscription immediately. That is the point: it is what you do when the old one has ended up somewhere it should not be.',
|
||
'int.mailToTask': 'Mail to task',
|
||
'int.mailHint':
|
||
'Send or forward an email here and the subject line is read as a quick add — “Invoice Anders p1 #Company” works here too.',
|
||
'int.noMailServer':
|
||
'No mail server is configured on this server, so the address below cannot receive anything yet. Set VERDANDE_SMTP_HOST and the other SMTP settings, and have the mail server deliver post for todo+* to /inbound/mail.',
|
||
'int.yourAddress': 'Your address',
|
||
'int.newAddress': 'New address',
|
||
'int.caldav': 'CalDAV',
|
||
'int.caldavHint':
|
||
'Two-way, unlike the feed above: Apple Reminders and Thunderbird can write back as well.',
|
||
'int.server': 'Server',
|
||
'int.caldavAuth':
|
||
'The username is your email. The password is an API token — not your real password.',
|
||
'int.gmailConnected': 'Gmail is connected.',
|
||
'int.gmailState': 'Google’s reply did not match the attempt that started here. Try again.',
|
||
'int.gmailExpired': 'The attempt expired on the way. Try again.',
|
||
'int.gmailInvalid': 'Google’s reply could not be read. Try again.',
|
||
'int.gmailFailed': 'Google refused to exchange the code.',
|
||
'int.gmailNoRefresh':
|
||
'Google sent no refresh token. Remove verdande from your Google account and connect again.',
|
||
'int.gmailDenied': 'Access was refused at Google.',
|
||
'int.gmailOther': 'Gmail replied: {what}',
|
||
|
||
// --- a project ----------------------------------------------------------------------
|
||
'project.name': 'The project’s name',
|
||
'project.clickToRename': 'Click to rename',
|
||
'project.color': 'The project’s colour',
|
||
'project.pickColor': 'Pick a colour',
|
||
'project.delete': 'Delete the project',
|
||
'project.share': 'Share',
|
||
'project.history': 'History',
|
||
'project.noHistory': 'Nothing has happened here yet.',
|
||
'project.canEdit': 'Can edit',
|
||
'project.canView': 'Can only view',
|
||
'project.invite': 'Invite',
|
||
'project.inviteByEmail': 'Invite by email',
|
||
'project.emailAddress': 'email address',
|
||
'project.role': 'Role',
|
||
'project.sendLinkYourself': 'No mail server configured — send the link yourself:',
|
||
'project.noSection': 'No section',
|
||
'project.rename': 'Rename',
|
||
'project.deleteSection': 'Delete',
|
||
'project.readOnly': 'You can see this project, but not change it.',
|
||
'project.notFound': 'That project does not exist, or you do not have access to it.',
|
||
'project.loadFailed': 'The project could not be loaded. The server did not answer.',
|
||
'project.retry': 'Try again',
|
||
'project.nothingHere': 'Nothing here yet.',
|
||
|
||
'project.sharedWith': 'Shared · {n}',
|
||
|
||
// --- the palette ---------------------------------------------------------------
|
||
'color.graphite': 'Graphite',
|
||
'color.tomato': 'Tomato',
|
||
'color.amber': 'Amber',
|
||
'color.lime': 'Lime',
|
||
'color.green': 'Green',
|
||
'color.teal': 'Teal',
|
||
'color.blue': 'Blue',
|
||
'color.indigo': 'Indigo',
|
||
'color.violet': 'Violet',
|
||
'color.magenta': 'Magenta',
|
||
|
||
// --- what a failed request says -----------------------------------------------
|
||
'error.unauthorized': 'Wrong email or password.',
|
||
'error.totpRequired': 'Enter the code from your authenticator.',
|
||
'error.forbidden': 'You do not have access to that.',
|
||
'error.notFound': 'Not found.',
|
||
'error.conflict': 'That already exists.',
|
||
'error.rateLimited': 'Too many attempts. Wait a moment.',
|
||
'error.validation': 'Check the fields below.',
|
||
'error.tooLarge': 'That is too large.',
|
||
'error.internal': 'Something went wrong. Try again.',
|
||
'error.badRequest': 'The request could not be read.',
|
||
'error.offline': 'No connection to the server.',
|
||
'error.gmailNotConfigured':
|
||
'Gmail is not set up on this server. An administrator has to register an OAuth client with Google under Settings → Integrations.',
|
||
'error.aiNotConfigured': 'No AI provider has been chosen. Set one up under Settings → AI.',
|
||
'error.totpNotEnabled': 'Two-factor is not on.',
|
||
'error.inboxProtected': 'The Inbox cannot be deleted.',
|
||
'error.lastAdmin':
|
||
'That is the last administrator. Make somebody else an administrator first — otherwise nobody can get back into this page.',
|
||
|
||
// --- the themes -------------------------------------------------------------------
|
||
'theme.dark': 'Northern lights',
|
||
'theme.darkNote': 'Dark, with a green accent.',
|
||
'theme.dusk': 'Dusk',
|
||
'theme.duskNote': 'Dark and warm, with amber.',
|
||
'theme.light': 'Daylight',
|
||
'theme.lightNote': 'Light and coolly neutral.',
|
||
'theme.paper': 'Paper',
|
||
'theme.paperNote': 'Light and warm, like paper.',
|
||
'theme.contrast': 'Contrast',
|
||
'theme.contrastNote': 'Black on white, for bright light.',
|
||
|
||
// --- push, in the browser's own words ----------------------------------------------
|
||
'push.deniedByBrowser':
|
||
'The browser has blocked notifications for this site. It has to be turned on in the browser’s own settings.',
|
||
'push.notEnabled': 'Notifications were not turned on.',
|
||
'push.noVapidKey': 'The server has no VAPID key.',
|
||
|
||
'view.done': 'Done',
|
||
'view.showDone': 'Show done',
|
||
'view.hideDone': 'Hide done'
|
||
};
|