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>
930 lines
44 KiB
JavaScript
930 lines
44 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 ---------------------------------------------------------------
|
||
'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.'
|
||
};
|