Files
verdande/web/src/lib/locales/en.js
T
Kristian WindandClaude Opus 5 aadfb197b6
Release / Build and publish image (push) Failing after 9m55s
feat(notes): arkiv, flere ad gangen, og en kopiér-knap på koden
**Arkivet, fordi papirkurven er det forkerte redskab.** Den siger "det her var en
fejl, og om tredive dage er det væk" — så alt, der er værd at beholde, kan ikke
komme i den. Et arkiv på tolv hundrede importerede noter er præcis det tilfælde,
hvor de fleste er værd at beholde og næsten ingen er værd at kigge på; uden et
sted at lægge dem er listen hele arkivet for evigt, og at finde ugens note
betyder at læse forbi et årti.

En kolonne, ikke en mappe. Noterne afviste et andet hierarki i 0018, og
arkivering er ikke arkivering i den forstand: det er én oplysning om, hvorvidt en
note er aktuel, og den skal kunne besvares ved siden af `deleted_at`, som er den
anden af samme slags. `updated_at` røres ikke — at rydde op er ikke at skrive.

**Flere ad gangen.** ⌘-klik lægger til, skift-klik tager intervallet i den
rækkefølge, listen faktisk står i — ikke notens egen, som er en anden, når der er
sorteret eller søgt. Trækker man en note, der er markeret, følger hele
markeringen med; trækker man en, der ikke er, er det den ene, for ellers ville et
træk i en tilfældig række flytte halvtreds noter, man havde glemt var markeret.

Ét kald frem for et pr. note: efter en import af tolv hundrede er det at vælge
halvtreds det almindelige tilfælde. Hver note kontrolleres stadig for sig — et sæt
er en bekvemmelighed for den, der kalder, og aldrig en vej til noget, de ikke
kunne have rørt én ad gangen.

En kant *og* en tone på det markerede: `--accent-sunken` er meget lys i lyst tema,
og tre markerede rækker var svære at skelne fra tre, der ikke var — en dårlig ting
at være i tvivl om lige inden man trykker Slet.

**Kopiér-knappen spiste næsten den kode, den skulle kopiere.** Den ligger inde i
`<pre>` for at kunne stå i hjørnet af blokken, og `colourCode` læste
`pre.textContent` og malede resultatet tilbage — så et klik på "Kopiér" skrev
"Kopieret" ind i koden, og næste gemning lagde det i filen. Nu ét sted, der siger
hvad en kodeblok indeholder, brugt af både farvelægningen og oversættelsen til
Markdown: var de to uenige, ville forskellen ende i noten. Prøven klikker,
læser udklipsholderen og læser filen bagefter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:03:02 +02:00

863 lines
40 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': 'Share in',
'notes.private': 'Only me',
'notes.shared': "The note is shared with the project's people.",
'notes.unshared': 'The note is yours again.',
'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.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}',
'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.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.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.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.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.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.'
};