Files
verdande/web/src/lib/locales/en.js
T
kristianwindandClaude Opus 5 9e3f20b050 feat(tasks): snooze — park a task at the bottom until a time
Snooze puts a task out of the way without finishing it or filing it: it sinks to
the bottom of its list and greys, and comes back on its own when its time passes.
No job wakes it — "snoozed" is just snoozed_until being still in the future, read
against the clock each time the list is ordered.

- `snoozed_until` on tasks (migration 0031); the list order sinks a still-snoozed
  task below everything, and it rejoins its place the moment the time passes.
- POST /tasks/{id}/snooze {until} — an RFC3339 time to snooze to, or empty to wake.
  Nothing else about the task moves; a snooze is about when you want to see it, not
  when it is due.
- The task drawer gains quick picks (this evening / tomorrow / next week) and a
  custom time, and a Wake now once it is snoozed. The row dims and carries a
  "snoozed until X" mark, and the list keeps snoozed tasks at the foot — the client
  sorts the same way the server does, so a drag-sorted list does not jump on reload.

Tests: snooze sinks a task below one made after it and waking returns it (Go); and
from the drawer the row greys, shows the mark, and un-greys on wake (e2e). Full
suites green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 10:59:34 +02:00

930 lines
44 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 ---------------------------------------------------------------
'notes.shareWith': 'File in project',
'notes.private': 'No project',
'notes.shared': "The note is shared with the project's people.",
'notes.unshared': 'The note is yours again.',
// Sharing with individual people, alongside the project.
'notes.groupSharedWithMe': 'Shared with me',
'notes.linkOpen': 'Open',
'notes.linkMissing': 'That note could not be found.',
'notes.sharedBy': 'Shared by {name}',
'notes.sharePeople': 'Share with people',
'notes.sharePick': 'Choose someone …',
'notes.shareButton': 'Share',
'notes.shareAdd': 'Add',
'notes.shareRoleViewer': 'Can view',
'notes.shareRoleEditor': 'Can edit',
'notes.shareRemove': 'Remove access',
'notes.shareNobody': 'Not shared with anyone yet.',
'notes.sharedToast': 'Shared with {name}.',
'notes.unsharedPerson': '{name} can no longer see the note.',
'notes.shareOwnerOnly': 'Only the owner can share the note.',
'notes.save': 'Save',
'notes.format': 'Format',
'notes.bold': 'Bold',
'notes.italic': 'Italic',
'notes.underline': 'Underline',
'notes.sortBy': 'Order',
'notes.sortUpdated': 'Last touched',
'notes.sortCreated': 'Created',
'notes.sortTitle': 'Name',
'beacon.title': 'Counting installations',
'beacon.hint': 'Once a day this installation reports that it exists, so the project can see how many people run it. It sends only the two values below — a random id and the version — and nothing else: no IP is stored, no names, no addresses, nothing about what you use it for. On by default, and you can turn it off here.',
'beacon.send': 'Send an anonymous daily report',
'beacon.exactly': 'Exactly this — nothing more:',
'beacon.lastPing': 'Last sent: {when}',
'beacon.collector': 'Collector address',
'beacon.collectorHint': 'Where the report goes. Leave it unless you run your own collector.',
'beacon.beCollector': 'This instance is the collector',
'beacon.receiving': 'Receiving anonymous reports — id and version, never an IP.',
'beacon.total': 'Total',
'beacon.active7': 'Active 7 days',
'beacon.active30': 'Active 30 days',
'beacon.byVersion': 'By version (30 days):',
'beacon.publish': 'Publish the count at /api/v1/beacon/count',
'beacon.publishHint': 'No login, cached for a minute. Reports installations seen in the last 30 days — not a running total, which would only ever climb.',
'notes.newHere': 'New note',
'notes.noneInProject': 'No notes yet. A note that mentions #theproject turns up here too.',
'notes.clearSearch': 'Clear the search',
'notes.found': '{count} notes',
'notes.picked': '{count} selected',
'notes.archive': 'Archive',
'notes.unarchive': 'Bring back',
'notes.clearPick': 'Clear selection',
'notes.showArchive': 'Show the archive',
'notes.deleteMany': 'Delete {count} notes?',
'notes.archiveOne': 'Archive this note',
'notes.emptyArchive': 'The archive is empty.',
'notes.copyCode': 'Copy',
'notes.copied': 'Copied',
'notes.copyFailed': 'Could not copy',
'notes.strike': 'Strikethrough',
'notes.showSource': 'Show as code',
'notes.source': 'Note source',
'notes.styleTitle': 'Title',
'notes.styleHeading': 'Heading',
'notes.styleSubheading': 'Subheading',
'notes.styleBody': 'Body',
'notes.styleMono': 'Monospaced',
'notes.styleQuote': 'Block quote',
'notes.styleBullet': 'Bulleted list',
'notes.styleNumbered': 'Numbered list',
'notes.favourite': 'Make a favourite',
'notes.unfavourite': 'Remove from favourites',
'notes.syntaxProject': 'project',
'notes.syntaxNote': 'another note',
'notes.yesterday': 'yesterday',
'notes.groupFavourites': 'Favourites',
'notes.groupToday': 'Today',
'notes.groupYesterday': 'Yesterday',
'notes.groupWeek': 'This week',
'notes.groupUnknown': 'No date',
'notes.title': 'Notes',
'notes.new': 'New note',
'notes.search': 'Search notes',
'notes.body': "The note's text",
'notes.placeholder': 'Write. #project and [[another note]] become references.',
'notes.untitled': 'Untitled',
'notes.noneYet': 'No notes yet.',
'notes.noneFound': 'No notes match.',
'notes.pickOne': 'Pick a note, or write a new one.',
'notes.saving': 'Saving …',
'notes.saved': 'Saved',
'notes.delete': 'Delete',
'notes.deleteNote': 'Delete "{name}"? It can be brought back from the trash.',
'palette.project': 'Project',
'palette.task': 'Task',
'palette.note': 'Note',
'nav.today': 'Today',
'nav.upcoming': 'Upcoming',
'nav.delegated': 'Waiting on others',
'nav.inbox': 'Inbox',
'nav.calendar': 'Calendar',
'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.syntaxProject': 'project',
'task.syntaxSection': 'section',
'task.syntaxLabel': 'label',
'task.syntaxPriority': 'priority',
'task.syntaxDateMark': 'tomorrow at 10',
'task.syntaxDate': 'due',
'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}',
'repeat.weekdays': 'weekdays',
'repeat.weekends': 'every weekend',
'repeat.and': 'and',
'repeat.everyDay': 'every {day}',
'repeat.everyNthWeekOn': 'every {n} weeks on {day}',
'repeat.everyOtherWeekOn': 'every other week on {day}',
'repeat.monthlyOn': 'the {day}th of every month',
'repeat.every.day': 'every day',
'repeat.every.week': 'every week',
'repeat.every.month': 'every month',
'repeat.every.year': 'every year',
'repeat.everyOther.day': 'every other day',
'repeat.everyOther.week': 'every other week',
'repeat.everyOther.month': 'every other month',
'repeat.everyOther.year': 'every other year',
'repeat.everyN.day': 'every {n} days',
'repeat.everyN.week': 'every {n} weeks',
'repeat.everyN.month': 'every {n} months',
'repeat.everyN.year': 'every {n} years',
'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.look': 'Appearance',
'account.lookHint': 'The theme says how bright. This says how it reads — typeface, air and corners. The two combine freely.',
'look.verdande': 'Verdande',
'look.verdandeNote': 'As you know it.',
'look.rolig': 'Calm',
'look.roligNote': 'Serif, more air. For reading and writing.',
'look.taet': 'Tight',
'look.taetNote': 'More rows on screen, less air.',
'look.terminal': 'Terminal',
'look.terminalNote': 'One width for everything. It all lines up.',
'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.test': 'Send a test',
'push.testing': 'Sending …',
'push.testTitle': 'Test from verdande',
'push.testBody': 'If this arrived, notifications work on this device.',
'push.testSent': 'Sent to {n} device(s). If nothing arrived, the browser or the operating system is holding it back — check that verdande is allowed to send notifications.',
'push.testNone': 'No devices are registered yet. Turn notifications on here first.',
'push.testFailed': '{service} refused it: {reason}',
'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',
'users.toProject': 'to {name}',
'users.toInstance': 'to the instance',
'users.invitedBy': 'invited by {name}',
// --- 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',
// What each recorded event is called. The keys are the server's and live in
// events.js; these are the words they become.
'event.projectCreated': 'created the project',
'event.projectUpdated': 'changed the project',
'event.projectDeleted': 'deleted the project',
'event.projectImported': 'imported the project',
'event.sectionCreated': 'created a section',
'event.sectionUpdated': 'changed a section',
'event.sectionDeleted': 'deleted a section',
'event.memberInvited': 'invited',
'event.memberAdded': 'added',
'event.memberRemoved': 'removed',
'event.memberRoleChanged': 'changed the role of',
'event.taskCreated': 'created a task',
'event.taskUpdated': 'changed a task',
'event.taskCompleted': 'closed a task',
'event.taskReopened': 'reopened a task',
'event.taskMoved': 'moved a task',
'event.taskDeleted': 'deleted a task',
'event.taskSplit': 'split a task up',
'event.commentCreated': 'wrote a comment',
'event.attachmentAdded': 'attached a file',
'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.doneHint': 'Everything you have closed, newest first. Closed something by mistake and it is here — and can be opened again.',
'data.openDone': 'Open finished tasks',
'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.importNotes': 'Import notes (a zip of Markdown files)',
'data.importNotesHint': 'One .md file per note; the first line becomes the title. Pictures beside the files come with them and are attached to the note. The script in tools/ gets your Apple Notes out.',
'data.notesImported': '{n} notes and {files} files brought in.',
'data.exportNotes': 'Every note as Markdown files',
'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.backupsSecrets':
'Tokens and mailbox passwords are encrypted inside the copy, and the key is not: it sits in a file beside the database. That is the point — a copy that goes astray does not open your mail. But restore a copy somewhere without that file and the mailboxes must be connected again.',
'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.foldSidebar': 'fold the sidebar away',
'keys.anyLetter': 'any letter',
'keys.typeToCapture': 'starts a task, with the letter already in the field',
'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.notFound': 'That task does not exist, or it is not yours.',
'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.dueTime': 'Time',
'detail.duration': 'Length in minutes',
'detail.snooze': 'Snooze',
'detail.wake': 'Wake now',
'detail.thisEvening': 'This evening',
'detail.tomorrow': 'Tomorrow',
'detail.nextWeek': 'Next week',
'detail.snoozeCustom': 'Pick a time',
'task.snoozedUntil': 'Snoozed until {when}',
'detail.durationShort': 'min',
'detail.labels': 'Labels',
'detail.labelsPlaceholder': 'separated by commas',
'detail.project': 'Project',
'detail.repeats': 'Repeats',
'detail.never': 'Never',
'detail.daily': 'Every day',
'detail.weekly': 'Every week',
'detail.fortnightly': 'Every other week',
'detail.monthly': 'Every month',
'detail.yearly': 'Every year',
'detail.asWritten': 'As written',
'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.mailboxes': 'Mailboxes',
'int.mailboxesHint':
'Flag an email in iCloud, Fastmail or any other provider and it becomes a task. One way: removing the flag does nothing to the task. Office 365 cannot be used here — Microsoft has closed IMAP to passwords.',
'int.addMailbox': 'Add a mailbox',
'int.host': 'IMAP server',
'int.username': 'Username',
'int.appPassword': 'App password',
'int.appPasswordHint':
'Not your ordinary password. iCloud and most others issue a separate one for applications, which you can withdraw on its own. It is stored encrypted.',
'int.connect': 'Connect',
'int.connecting': 'Connecting …',
'int.disconnect': 'Disconnect',
'int.disconnectMailbox': 'Disconnect {name}? The tasks it made will stay.',
'int.mailboxConnected': 'The mailbox is connected.',
'int.fetchNow': 'Fetch now',
'int.fetching': 'Fetching …',
'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 and paste the two values here. The same client serves both Gmail and Google Calendar: enable whichever API you need, and add both redirect URIs — the one below and the calendar\'s, which is shown in the Google Calendar panel. 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.calendar': 'Google Calendar',
'int.calendarHint':
'Read, never written. The events appear in Calendar beside your own tasks with due dates — they cannot be moved or completed from here, because they belong to Google.',
'int.connectCalendar': 'Connect Google Calendar',
'int.calendarConnectedAs': 'Connected as',
'int.calendarSameClient':
'Uses the same OAuth client as Gmail above. The Google Calendar API has to be enabled, and this address has to be listed as an authorised redirect URI.',
'int.whichCalendars': 'Which calendars are shown',
'int.noCalendars': 'The account has no calendars to show.',
'int.subscriptions': 'Subscriptions',
'int.subscriptionsHint': 'A calendar you only have an address to — holidays, a club, a shared calendar from work. Fetched every fifteen minutes, and read-only. webcal:// addresses work; they are https with a different scheme in front.',
'int.subscriptionURL': "The calendar's address",
'int.subscribe': 'Subscribe',
'int.subscribing': 'Fetching …',
'int.unsubscribe': 'Remove',
'int.unsubscribeQuestion': 'Stop subscribing to "{name}"? Its events go from the calendar.',
'int.calendarLastSync': 'Last fetched {when}.',
'int.calendarNeverSynced': 'Not fetched yet.',
'int.calendarFetchedOne': '{n} event fetched.',
'int.calendarFetchedMany': '{n} events fetched.',
'int.calendarNothingNew': 'No events in the window.',
'int.calendarConnected': 'Google Calendar is connected.',
'int.calendarState': 'Google’s reply did not match the attempt that started here. Try again.',
'int.calendarExpired': 'The attempt expired on the way. Try again.',
'int.calendarInvalid': 'Google’s reply could not be read. Try again.',
'int.calendarFailed': 'Google refused to exchange the code.',
'int.calendarNoRefresh':
'Google sent no refresh token. Remove verdande from your Google account and connect again.',
'int.calendarDenied': 'Access was refused at Google.',
'int.calendarOrgInternal':
'The Google app is registered as Internal and only accepts accounts inside the organisation. A private account cannot consent to it, whatever the screen says.',
'int.calendarOther': 'Google replied: {what}',
'int.gmailFetchedOne': '{n} task fetched.',
'int.gmailFetchedMany': '{n} tasks fetched.',
'int.gmailNothingNew': 'No new messages.',
'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}',
// --- the calendar, with Google’s events laid over ------------------------------------
'cal.title': 'Calendar',
'cal.notConnected': 'No calendar is connected yet. That is done under Settings → Integrations.',
'cal.settings': 'Settings',
'cal.noneChosen': 'None of the account’s calendars are chosen yet.',
'cal.allDay': 'All day',
'cal.resize': 'Drag to change the length',
'cal.openInGoogle': 'Open in Google Calendar',
'cal.beyondWindow':
'Events are kept from {from} to {to}. Outside that the grid shows only your own tasks.',
'cal.eventCannotMove': '{name} — an event in {calendar}. It cannot be moved from here.',
// --- 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',
'task.moveFailed': 'The task could not be moved.',
'push.updatesOff': 'Updates are not being checked for.',
'push.updateOut': '{version} is out.',
'push.upToDate': 'No newer version found.',
'view.nothingMoreToday': 'Nothing more today.',
'view.overdue': 'Overdue',
'project.roleFor': 'Role for {name}',
'project.more': 'More actions',
'project.removeMember': 'Remove {name}',
'nav.closeMenu': 'Close menu',
'view.addTask': '+ Add a task',
'view.addTaskHere': 'Write a task — a date, p1 and @label work here too',
'view.addTaskIn': 'New task in {name}',
'task.completedUndo': '“{what}” is done.',
'task.undo': 'Undo',
'done.title': 'Done',
'done.hint': 'Everything you have closed, newest first. Click the circle to reopen one.',
'done.empty': 'You have not closed anything yet.',
'done.unknownDay': 'No date',
// --- passkeys ----------------------------------------------------------------------
'passkey.title': 'Passkeys',
'passkey.hint':
'A key on a device instead of a secret in a head. The private half never leaves the device, and the signature is bound to the address that asked for it — so a convincing copy of the sign-in page somewhere else gets nothing.',
'passkey.unsupported': 'This browser does not support passkeys.',
'passkey.notAccepted':
'That key was not accepted. If it worked before, the address has usually changed — a passkey belongs to the exact address it was made on, scheme and all.',
'passkey.none': 'No keys yet.',
'passkey.name': 'Name for the key',
'passkey.namePlaceholder': 'e.g. my laptop',
'passkey.add': 'Add a key',
'passkey.registering': 'Waiting for the device…',
'passkey.remove': 'Remove',
'passkey.removeQuestion': 'Remove "{name}"? That device cannot sign in with it afterwards.',
'passkey.bothFactors': 'both factors',
'passkey.possessionOnly': 'possession only — the code is still required',
'passkey.lastUsed': 'last used {when}',
'passkey.neverUsed': 'never used',
'passkey.signIn': 'Sign in with a passkey',
'passkey.signingIn': 'Waiting for the device…',
'passkey.unavailable':
'Passkeys cannot be used here — either this browser does not support them, or the server’s address is an IP rather than a domain name, which no authenticator will accept.',
// --- restarting from the interface ---------------------------------------------
'update.restart': 'Restart and pull the latest version',
'update.restarting': 'Restarting…',
'update.restartQuestion':
'Restart now? The server is down for about half a minute, and pulls the newest version on the way back up.',
'update.asking': 'Asking the panel to restart…',
'update.waiting': 'Waiting for the server to come back…',
'update.stillDown': 'The server has not come back yet. Have a look in the panel.',
'update.notConfigured':
'This instance cannot restart itself: {missing} are not set. A container cannot replace its own image — the panel does the work, and this is only the way to ask.',
'error.gmailSaid': 'Gmail said: {what}',
'error.calendarRefused': 'Google refused the call. Try connecting the calendar again.',
'error.calendarSaid': 'Google replied: {what}',
'int.disconnectCalendarQuestion':
'Disconnect Google Calendar? The tokens are forgotten and the fetched copy of the events is deleted. Your own tasks are untouched.',
'error.mailboxRefused': 'The mailbox could not be read.',
'error.mailboxSaid': 'The mailbox answered: {what}',
'error.gmailRefused': 'Gmail refused the call. Try connecting the mailbox again.',
'int.newAddressQuestion': 'A new address? Mail to the old one is refused.',
'int.copied': 'Copied.',
'int.copyFailed': 'Could not copy — select the text instead.',
'detail.deleteComment': 'Delete the comment?',
'int.disconnectQuestion': 'Disconnect Gmail? The tokens are forgotten.',
'int.newLinkQuestion': 'A new feed link? Every existing subscription stops working.',
'data.deleteTemplate': 'Delete the template "{name}"?',
'tokens.revokeQuestion': 'Revoke "{name}"? Everything using it stops working.',
'account.signOutThisDevice': 'That is this device. You will be signed out.',
'project.deleteQuestion': 'Delete "{name}" with all its tasks?',
'project.deleteRecoverable':
'It goes to the trash and can be restored under Settings → Data and templates for {days} days.'
};