Merge branch 'main' into #875

This commit is contained in:
Nariman Jelveh 2024-12-12 10:58:47 -08:00 committed by GitHub
commit a879c83369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 509 additions and 512 deletions

View File

@ -37,10 +37,16 @@ const implicit_user_permissions = {
/**
* The PermissionService class manages the core functionality for handling permissions within the Puter ecosystem.
* It provides methods for granting, revoking, and checking permissions for various entities such as users and applications.
* This service interacts with the database to store, retrieve, and audit permission changes, and also handles complex permission logic like rewriting, implication, and explosion of permissions.
*/
* Permission rewriters are used to map one set of permission strings to another.
* These are invoked during permission scanning and when permissions are granted or revoked.
*
* For example, Puter's filesystem uses this to map 'fs:/some/path:mode' to
* 'fs:SOME-UUID:mode'.
*
* A rewriter is constructed using the static method PermissionRewriter.create({ matcher, rewriter }).
* The matcher is a function that takes a permission string and returns true if the rewriter should be applied.
* The rewriter is a function that takes a permission string and returns the rewritten permission string.
*/
class PermissionRewriter {
static create ({ id, matcher, rewriter }) {
return new PermissionRewriter({ id, matcher, rewriter });
@ -70,11 +76,17 @@ class PermissionRewriter {
/**
* The PermissionImplicator class is used to manage implicit permissions.
* It defines methods to match and check if a given permission is implicitly granted to an actor.
* @class
* @name PermissionImplicator
*/
* Permission implicators are used to manage implicit permissions.
* It defines a method to check if a given permission is implicitly granted to an actor.
*
* For example, Puter's filesystem uses this to grant permission to a file if the specified
* 'actor' is the owner of the file.
*
* An implicator is constructed using the static method PermissionImplicator.create({ matcher, checker }).
* `matcher is a function that takes a permission string and returns true if the implicator should be applied.
* `checker` is a function that takes an actor and a permission string and returns true if the permission is implied.
* The actor and permission are passed to checker({ actor, permission }) as an object.
*/
class PermissionImplicator {
static create ({ id, matcher, checker }) {
return new PermissionImplicator({ id, matcher, checker });
@ -108,12 +120,17 @@ class PermissionImplicator {
/**
* The PermissionExploder class is responsible for expanding permissions into a set of more granular permissions.
* It uses a matcher function to determine if a permission should be exploded and an exploder function to perform the expansion.
* This class is part of the permission management system, allowing for dynamic and complex permission structures.
*
* @class PermissionExploder
*/
* Permission exploders are used to map any permission to a list of permissions
* which are considered to imply the specified permission.
*
* It uses a matcher function to determine if a permission should be exploded
* and an exploder function to perform the expansion.
*
* The exploder is constructed using the static method PermissionExploder.create({ matcher, explode }).
* The `matcher` is a function that takes a permission string and returns true if the exploder should be applied.
* The `explode` is a function that takes an actor and a permission string and returns a list of implied permissions.
* The actor and permission are passed to explode({ actor, permission }) as an object.
*/
class PermissionExploder {
static create ({ id, matcher, exploder }) {
return new PermissionExploder({ id, matcher, exploder });
@ -952,14 +969,26 @@ class PermissionService extends BaseService {
}
register_rewriter (translator) {
if ( ! (translator instanceof PermissionRewriter) ) {
throw new Error('translator must be a PermissionRewriter');
/**
* Register a permission rewriter. For details see the documentation on the
* PermissionRewriter class.
*
* @param {PermissionRewriter} rewriter - The permission rewriter to register
*/
register_rewriter (rewriter) {
if ( ! (rewriter instanceof PermissionRewriter) ) {
throw new Error('rewriter must be a PermissionRewriter');
}
this._permission_rewriters.push(translator);
this._permission_rewriters.push(rewriter);
}
/**
* Register a permission implicator. For details see the documentation on the
* PermissionImplicator class.
*
* @param {PermissionImplicator} implicator - The permission implicator to register
*/
register_implicator (implicator) {
if ( ! (implicator instanceof PermissionImplicator) ) {
throw new Error('implicator must be a PermissionImplicator');
@ -968,6 +997,12 @@ class PermissionService extends BaseService {
this._permission_implicators.push(implicator);
}
/**
* Register a permission exploder. For details see the documentation on the
* PermissionExploder class.
*
* @param {PermissionExploder} exploder - The permission exploder to register
*/
register_exploder (exploder) {
if ( ! (exploder instanceof PermissionExploder) ) {
throw new Error('exploder must be a PermissionExploder');

View File

@ -21,6 +21,14 @@ class VirtualGroupService extends BaseService {
this.membership_implicators_ = [];
}
/**
* Registers a function that reports one or more groups that an actor
* should be considered a member of.
*
* @note this only applies to virtual groups, not persistent groups.
*
* @param {*} implicator
*/
register_membership_implicator (implicator) {
this.membership_implicators_.push(implicator);
}

View File

@ -10,11 +10,7 @@
const remove_paths_through_user = ({ reading, user }) => {
const no_cycle_reading = [];
for ( let i = 0 ; i < reading.length ; i++ ) {
const node = reading[i];
console.log('checking node...', node);
for ( const node of reading ) {
if ( node.$ === 'path' ) {
if (
node.issuer_username === user.username
@ -33,14 +29,11 @@ const remove_paths_through_user = ({ reading, user }) => {
no_cycle_reading.push(node);
}
console.log('\x1B[36;1m ====', reading.length - no_cycle_reading.length, 'nodes filtered out ====\x1B[0m');
return no_cycle_reading;
};
const reading_has_terminal = ({ reading }) => {
for ( let i = 0 ; i < reading.length ; i++ ) {
const node = reading[i];
for ( const node of reading ) {
if ( node.has_terminal ) {
return true;
}

View File

@ -14,9 +14,26 @@ const { reading_has_terminal } = require("./permission-scan-lib");
"Ctrl+K, Ctrl+J" or "⌘K, ⌘J";
*/
/**
* Permission Scanners
* @usedBy scan-permission.js
*
* These are all the different ways an entity (user or app) can have a permission.
* This list of scanners is iterated over and invoked by scan-permission.js.
*
* Each `scan` function is passed a sequence scope. The instance attached to the
* sequence scope is PermissionService itself, so any `a.iget('something')` is
* accessing the member 'something' of the PermissionService instance.
*/
const PERMISSION_SCANNERS = [
{
name: 'implied',
documentation: `
Scans for permissions that are implied by "permission implicators".
Permission implicators are added by other services via
PermissionService's \`register_implicator\` method.
`,
async scan (a) {
const reading = a.get('reading');
const { actor, permission_options } = a.values();
@ -46,6 +63,9 @@ const PERMISSION_SCANNERS = [
},
{
name: 'user-user',
documentation: `
User-to-User permissions are permission granted form one user to another.
`,
async scan (a) {
const { reading, actor, permission_options, state } = a.values();
if ( !(actor.type instanceof UserActorType) ) {
@ -96,7 +116,6 @@ const PERMISSION_SCANNERS = [
if ( should_continue ) continue;
// const issuer_perm = await this.check(issuer_actor, row.permission);
const issuer_reading = await a.icall(
'scan', issuer_actor, row.permission, undefined, state);
@ -117,6 +136,13 @@ const PERMISSION_SCANNERS = [
},
{
name: 'hc-user-group-user',
documentation: `
These are user-to-group permissions that are defined in the
hardcoded_user_group_permissions section of "hardcoded-permissions.js".
These are typically used to grant permissions from the system user to
the default groups: "admin", "user", and "temp".
`,
async scan (a) {
const { reading, actor, permission_options } = a.values();
if ( !(actor.type instanceof UserActorType) ) {
@ -167,6 +193,11 @@ const PERMISSION_SCANNERS = [
},
{
name: 'user-group-user',
documentation: `
This scans for permissions that are granted to the user because a
group they are a member of was granted this permission by another
user.
`,
async scan (a) {
const { reading, actor, permission_options } = a.values();
if ( !(actor.type instanceof UserActorType) ) {
@ -223,6 +254,16 @@ const PERMISSION_SCANNERS = [
},
{
name: 'user-virtual-group-user',
documentation: `
These are groups with computed membership. Permissions are not granted
to these groups; instead the groups are defined with a list of
permissions that are granted to the group members.
Services can define "virtual groups" via the "virtual-group" service.
Services can also register membership implicators for virtual groups
which will compute on the fly whether or not an actor should be
considered a member of the group.
`,
async scan (a) {
const svc_virtualGroup = await a.iget('services').get('virtual-group');
const { reading, actor, permission_options } = a.values();
@ -248,6 +289,10 @@ const PERMISSION_SCANNERS = [
},
{
name: 'user-app',
documentation: `
If the actor is an app, this scans for permissions granted to the app
because the user has the permission and granted it to the app.
`,
async scan (a) {
const { reading, actor, permission_options } = a.values();
if ( !(actor.type instanceof AppUnderUserActorType) ) {

View File

@ -52,7 +52,6 @@ class Context {
);
}
// x = globalThis.root_context ?? this.create({});
x = this.root.sub({}, this.USE_NAME_FALLBACK);
}
if ( x && k ) return x.get(k);
@ -180,7 +179,6 @@ class Context {
});
}
abind (cb) {
const als = this.constructor.contextAsyncLocalStorage;
return async (...args) => {
return await this.arun(async () => {
return await cb(...args);

View File

@ -16,15 +16,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// function fuzz_number(n) {
// if (n === 0) return 0;
// // let randomized = n + (Math.random() - 0.5) * n * 0.2;
// let randomized = n;
// let magnitude = Math.floor(Math.log10(randomized));
// let factor = Math.pow(10, magnitude);
// return Math.round(randomized / factor) * factor;
// }
function fuzz_number(num) {
// If the number is 0, then return 0
@ -46,47 +37,6 @@ function fuzz_number(num) {
return Math.round(num / factor) * factor;
}
// function fuzz_number(number) {
// if (isNaN(number)) {
// return 'Invalid number';
// }
// let formattedNumber;
// if (number >= 1000000) {
// // For millions, we want to show one decimal place
// formattedNumber = (number / 1000000).toFixed(0) + 'm';
// } else if (number >= 1000) {
// // For thousands, we want to show one decimal place
// formattedNumber = (number / 1000).toFixed(0) + 'k';
// } else if (number >= 500) {
// // For hundreds, we want to show no decimal places
// formattedNumber = '500+';
// } else if (number >= 100) {
// // For hundreds, we want to show no decimal places
// formattedNumber = '100+';
// } else if (number >= 50) {
// // For hundreds, we want to show no decimal places
// formattedNumber = '50+';
// } else if (number >= 10) {
// // For hundreds, we want to show no decimal places
// formattedNumber = '10+';
// }
// else {
// // For numbers less than 10, we show the number as is.
// formattedNumber = '1+';
// }
// // If the decimal place is 0 (e.g., 5.0k), we remove the decimal part (to have 5k instead)
// formattedNumber = formattedNumber.replace(/\.0(?=[k|m])/, '');
// // Append the plus sign for numbers 1000 and greater, denoting the number is 'this value or more'.
// if (number >= 1000) {
// formattedNumber += '+';
// }
// return formattedNumber;
// }
module.exports = {
fuzz_number
};

View File

@ -18,6 +18,9 @@
*/
const { TeePromise } = require('@heyputer/putility').libs.promise;
/**
* RWLock is a read-write lock that allows multiple readers or a single writer.
*/
class RWLock {
static TYPE_READ = Symbol('read');
static TYPE_WRITE = Symbol('write');
@ -45,11 +48,6 @@ class RWLock {
this.check_queue_();
}
check_queue_ () {
// console.log('check_queue_', {
// readers_: this.readers_,
// writer_: this.writer_,
// queue: this.queue.map(item => item.type),
// });
if ( this.queue.length === 0 ) {
if ( this.readers_ === 0 && ! this.writer_ ) {
this.on_empty_();

View File

@ -100,7 +100,6 @@ class ParallelTasks {
return;
}
// const span = this.tracer.startSpan(name);
this.promises.push(this.run_(name, fn));
}

View File

@ -1,21 +0,0 @@
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class QueueBatcher {
//
}

View File

@ -52,7 +52,7 @@ const simple_retry = async function simple_retry (func, max_tries, interval) {
};
const poll = async function poll({ poll_fn, schedule_fn }) {
let delay = undefined;
let delay;
while ( true ) {
const is_done = await poll_fn();

View File

@ -18,7 +18,6 @@
*/
const { PassThrough, Readable, Transform } = require('stream');
const { TeePromise } = require('@heyputer/putility').libs.promise;
const { EWMA } = require('./opmath');
class StreamBuffer extends TeePromise {
constructor () {
@ -47,6 +46,15 @@ const stream_to_the_void = stream => {
stream.on('error', () => {});
};
/**
* This will split a stream (on the read side) into `n` streams.
* The slowest reader will determine the speed the the source stream
* is consumed at to avoid buffering.
*
* @param {*} source
* @param {*} n
* @returns
*/
const pausing_tee = (source, n) => {
const { PassThrough } = require('stream');
@ -59,39 +67,31 @@ const pausing_tee = (source, n) => {
streams_.push(stream);
stream.on('drain', () => {
ready_[i] = true;
// console.log(source.id, 'PR :: drain from reader', i, ready_);
if ( first_ ) {
source.resume();
first_ = false;
}
if (ready_.every(v => !! v)) source.resume();
});
// stream.on('newListener', (event, listener) => {
// console.log('PR :: newListener', i, event, listener);
// });
}
source.on('data', (chunk) => {
// console.log(source.id, 'PT :: data from source', chunk.length);
ready_.forEach((v, i) => {
ready_[i] = streams_[i].write(chunk);
});
if ( ! ready_.every(v => !! v) ) {
// console.log('PT :: pausing source', ready_);
source.pause();
return;
}
});
source.on('end', () => {
// console.log(source.id, 'PT :: end from source');
for ( let i=0 ; i < n ; i++ ) {
streams_[i].end();
}
});
source.on('error', (err) => {
// console.log(source.id, 'PT :: error from source', err);
for ( let i=0 ; i < n ; i++ ) {
streams_[i].emit('error', err);
}
@ -100,6 +100,9 @@ const pausing_tee = (source, n) => {
return streams_;
};
/**
* A debugging stream transform that logs the data it receives.
*/
class LoggingStream extends Transform {
constructor(options) {
super(options);
@ -431,9 +434,7 @@ async function* chunk_stream(
offset += amount;
while (offset >= chunk_size) {
console.log('start yield');
yield buffer;
console.log('end yield');
buffer = Buffer.alloc(chunk_size);
offset = 0;
@ -449,13 +450,8 @@ async function* chunk_stream(
if ( chunk_time_ewma !== null ) {
const chunk_time = chunk_time_ewma.get();
// const sleep_time = chunk_size * chunk_time;
const sleep_time = (chunk.length / chunk_size) * chunk_time / 2;
// const sleep_time = (amount / chunk_size) * chunk_time;
// const sleep_time = (amount / chunk_size) * chunk_time;
console.log(`start sleep ${amount} / ${chunk_size} * ${chunk_time} = ${sleep_time}`);
await new Promise(resolve => setTimeout(resolve, sleep_time));
console.log('end sleep');
}
}

View File

@ -21,7 +21,7 @@ const valid_file_size = v => {
if ( ! Number.isInteger(v) ) {
return { ok: false, v };
}
if ( ! (v >= 0) ) {
if ( v < 0 ) {
return { ok: false, v };
}
return { ok: true, v };

View File

@ -28,8 +28,7 @@ class WorkList {
clear_invalid () {
const new_items = [];
for ( let i=0 ; i < this.items.length ; i++ ) {
const item = this.items[i];
for ( const item of this.items ) {
if ( item.invalid ) continue;
new_items.push(item);
}

View File

@ -18,399 +18,396 @@
*/
const da = {
name: "Dansk",
english_name: "Danish",
code: "da",
dictionary: {
about: "Om",
account: "Konto",
account_password: "Bekræft kontoens adgangskode",
access_granted_to: "Adgang givet til",
add_existing_account: "Tilføj eksisterende konto",
all_fields_required: 'Alle felter er påkrævede.',
allow: 'Tillad',
apply: "Anvend",
ascending: 'Stigende',
associated_websites: "Tilknyttede websteder",
auto_arrange: 'Auto Arrangere',
background: "Baggrund",
browse: "Gennemse",
cancel: 'Annuller',
center: 'Center',
change_desktop_background: 'Ændre skrivebordsbaggrund…',
change_email: "Ændre e-mail",
change_language: "Ændre sprog",
change_password: "Ændre adgangskode",
change_ui_colors: "Ændre UI-farver",
change_username: "Ændre brugernavn",
close: 'Luk',
close_all_windows: "Luk alle vinduer",
close_all_windows_confirm: "Er du sikker på, at du vil lukke alle vinduer?",
close_all_windows_and_log_out: 'Luk vinduer og log ud',
change_always_open_with: "Vil du altid åbne denne filtype med",
color: 'Farve',
confirm: 'Bekræft',
confirm_2fa_setup: 'Jeg har tilføjet koden til min autentifikator-app',
confirm_2fa_recovery: 'Jeg har gemt mine gendannelseskoder på et sikkert sted',
confirm_account_for_free_referral_storage_c2a: 'Opret en konto og bekræft din e-mailadresse for at modtage 1 GB gratis lagerplads. Din ven vil også få 1 GB gratis lagerplads.',
confirm_code_generic_incorrect: "Forkert kode.",
confirm_code_generic_too_many_requests: "For mange anmodninger. Vent et par minutter.",
confirm_code_generic_submit: "Indsend kode",
confirm_code_generic_try_again: "Prøv igen",
confirm_code_generic_title: "Indtast bekræftelseskode",
confirm_code_2fa_instruction: "Indtast den 6-cifrede kode fra din autentifikator-app.",
confirm_code_2fa_submit_btn: "Indsend",
confirm_code_2fa_title: "Indtast 2FA-kode",
confirm_delete_multiple_items: 'Er du sikker på, at du vil slette disse elementer permanent?',
confirm_delete_single_item: 'Vil du slette dette element permanent?',
confirm_open_apps_log_out: 'Du har åbne apps. Er du sikker på, at du vil logge ud?',
confirm_new_password: "Bekræft ny adgangskode",
confirm_delete_user: "Er du sikker på, at du vil slette din konto? Alle dine filer og data vil blive permanent slettet. Denne handling kan ikke fortrydes.",
confirm_delete_user_title: "Slet konto?",
confirm_session_revoke: "Er du sikker på, at du vil tilbagekalde denne session?",
confirm_your_email_address: "Bekræft din e-mailadresse",
contact_us: "Kontakt os",
contact_us_verification_required: "Du skal have en verificeret e-mailadresse for at bruge dette.",
contain: 'Indeholde',
continue: "Fortsæt",
copy: 'Kopier',
copy_link: "Kopier link",
copying: "Kopierer",
copying_file: "Kopierer %%",
cover: 'Omslag',
create_account: "Opret konto",
create_free_account: "Opret gratis konto",
create_shortcut: "Opret genvej",
credits: "Kreditter",
current_password: "Nuværende adgangskode",
cut: 'Klip',
clock: "Uret",
clock_visible_hide: 'Skjul - Altid skjult',
clock_visible_show: 'Vis - Altid synlig',
clock_visible_auto: 'Auto - Standard, synlig kun i fuld skærm-tilstand.',
close_all: 'Luk alle',
created: 'Oprettet',
date_modified: 'Dato ændret',
default: 'Standard',
delete: 'Slet',
delete_account: "Slet konto",
delete_permanently: "Slet permanent",
deleting_file: "Sletter %%",
deploy_as_app: 'Implementer som app',
descending: 'Faldende',
desktop: 'Skrivebord',
desktop_background_fit: "Tilpas",
developers: "Udviklere",
dir_published_as_website: `%strong% er blevet offentliggjort til:`,
disable_2fa: 'Deaktiver 2FA',
disable_2fa_confirm: "Er du sikker på, at du vil deaktivere 2FA?",
disable_2fa_instructions: "Indtast din adgangskode for at deaktivere 2FA.",
disassociate_dir: "Fjern tilknytning til mappe",
documents: 'Dokumenter',
dont_allow: 'Tillad ikke',
download: 'Download',
download_file: 'Download fil',
downloading: "Downloader",
email: "E-mail",
email_change_confirmation_sent: "En bekræftelses-e-mail er sendt til din nye e-mailadresse. Kontroller din indbakke og følg instruktionerne for at fuldføre processen.",
email_invalid: 'E-mail er ugyldig.',
email_or_username: "E-mail eller brugernavn",
email_required: 'E-mail er påkrævet.',
empty_trash: 'Tøm papirkurv',
empty_trash_confirmation: `Er du sikker på, at du vil slette elementerne i papirkurven permanent?`,
emptying_trash: 'Tømmer papirkurv…',
enable_2fa: 'Aktivér 2FA',
end_hard: "Afslut hårdt",
end_process_force_confirm: "Er du sikker på, at du vil afslutte denne proces tvangsmæssigt?",
end_soft: "Afslut blødt",
enlarged_qr_code: "Forstørret QR-kode",
enter_password_to_confirm_delete_user: "Indtast din adgangskode for at bekræfte sletning af konto",
error_message_is_missing: "Fejlmeddelelse mangler.",
error_unknown_cause: "Der opstod en ukendt fejl.",
error_uploading_files: "Fejl ved upload af filer",
favorites: "Favoritter",
feedback: "Feedback",
feedback_c2a: "Brug venligst formularen nedenfor til at sende os din feedback, kommentarer og fejlrapporter.",
feedback_sent_confirmation: "Tak fordi du kontaktede os. Hvis du har en e-mail knyttet til din konto, vil du høre fra os så hurtigt som muligt.",
fit: "Pas",
folder: 'Mappe',
force_quit: 'Tving afslut',
forgot_pass_c2a: "Glemt adgangskode?",
from: "Fra",
general: "Generelt",
get_a_copy_of_on_puter: `Få en kopi af '%%' på Puter.com!`,
get_copy_link: 'Få kopi-link',
hide_all_windows: "Skjul alle vinduer",
home: 'Hjem',
html_document: 'HTML-dokument',
hue: 'Farvetone',
image: 'Billede',
incorrect_password: "Forkert adgangskode",
invite_link: "Inviter link",
item: 'element',
items_in_trash_cannot_be_renamed: `Dette element kan ikke omdøbes, fordi det er i papirkurven. For at omdøbe dette element, skal du først trække det ud af papirkurven.`,
jpeg_image: 'JPEG-billede',
keep_in_taskbar: 'Hold i proceslinjen',
language: "Sprog",
license: "Licens",
lightness: 'Lysstyrke',
link_copied: "Link kopieret",
loading: 'Indlæser',
log_in: "Log ind",
log_into_another_account_anyway: 'Log ind på en anden konto alligevel',
log_out: 'Log ud',
looks_good: "Ser godt ud!",
manage_sessions: "Administrer sessioner",
menubar_style: "Menubjælke stil",
menubar_style_desktop: "Skrivebord",
menubar_style_system: "System",
menubar_style_window: "Vindue",
modified: 'Ændret',
move: 'Flyt',
moving_file: "Flytter %%",
my_websites: "Mine websteder",
name: 'Navn',
name_cannot_be_empty: 'Navn kan ikke være tomt.',
name_cannot_contain_double_period: "Navn kan ikke være '..' tegn.",
name_cannot_contain_period: "Navn kan ikke være '.' tegn.",
name_cannot_contain_slash: "Navn kan ikke indeholde '/' tegn.",
name_must_be_string: "Navn kan kun være en streng.",
name_too_long: `Navn kan ikke være længere end %% tegn.`,
new: 'Ny',
new_email: 'Ny e-mail',
new_folder: 'Ny mappe',
new_password: "Ny adgangskode",
new_username: "Nyt brugernavn",
no: 'Nej',
no_dir_associated_with_site: 'Ingen mappe tilknyttet denne adresse.',
no_websites_published: "Du har ikke offentliggjort nogen websteder endnu. Højreklik på en mappe for at komme i gang.",
ok: 'OK',
open: "Åbn",
open_in_new_tab: "Åbn i ny fane",
open_in_new_window: "Åbn i nyt vindue",
open_with: "Åbn med",
original_name: 'Originalt navn',
original_path: 'Original sti',
oss_code_and_content: "Open Source Software og indhold",
password: "Adgangskode",
password_changed: "Adgangskode ændret.",
password_recovery_rate_limit: "Du har nået vores rate-limit; vent venligst et par minutter. For at undgå dette i fremtiden, undgå at genindlæse siden for mange gange.",
password_recovery_token_invalid: "Denne adgangskodegendannelsestoken er ikke længere gyldig.",
password_recovery_unknown_error: "Der opstod en ukendt fejl. Prøv venligst igen senere.",
password_required: 'Adgangskode er påkrævet.',
password_strength_error: "Adgangskoden skal være mindst 8 tegn lang og indeholde mindst ét stort bogstav, ét lille bogstav, ét tal og ét specialtegn.",
passwords_do_not_match: '`Ny adgangskode` og `Bekræft ny adgangskode` stemmer ikke overens.',
paste: 'Sæt ind',
paste_into_folder: "Sæt ind i mappe",
path: 'Sti',
personalization: "Personalisering",
pick_name_for_website: "Vælg et navn til dit websted:",
picture: "Billede",
pictures: 'Billeder',
plural_suffix: 's',
powered_by_puter_js: `Udviklet af {{link=docs}}Puter.js{{/link}}`,
preparing: "Forbereder...",
preparing_for_upload: "Forbereder til upload...",
print: 'Udskriv',
privacy: "Privatliv",
proceed_to_login: 'Fortsæt til login',
proceed_with_account_deletion: "Fortsæt med sletning af konto",
process_status_initializing: "Initialiserer",
process_status_running: "Kører",
process_type_app: 'App',
process_type_init: 'Init',
process_type_ui: 'UI',
properties: "Egenskaber",
public: 'Offentlig',
publish: "Offentliggør",
publish_as_website: 'Offentliggør som websted',
puter_description: `Puter er en privatlivsorienteret personlig sky til at holde alle dine filer, apps og spil på ét sikkert sted, tilgængeligt fra hvor som helst, når som helst.`,
reading_file: "Læser %strong%",
recent: "Seneste",
recommended: "Anbefalet",
recover_password: "Gendan adgangskode",
refer_friends_c2a: "Få 1 GB for hver ven, der opretter og bekræfter en konto på Puter. Din ven vil også få 1 GB!",
refer_friends_social_media_c2a: `Få 1 GB gratis lagerplads på Puter.com!`,
refresh: 'Opdater',
release_address_confirmation: `Er du sikker på, at du vil frigive denne adresse?`,
remove_from_taskbar: 'Fjern fra proceslinje',
rename: 'Omdøb',
repeat: 'Gentag',
replace: 'Erstat',
replace_all: 'Erstat alle',
resend_confirmation_code: "Send bekræftelseskode igen",
reset_colors: "Nulstil farver",
restart_puter_confirm: "Er du sikker på, at du vil genstarte Puter?",
restore: "Gendan",
save: 'Gem',
saturation: 'Mætning',
save_account: 'Gem konto',
save_account_to_get_copy_link: "Opret venligst en konto for at fortsætte.",
save_account_to_publish: 'Opret venligst en konto for at fortsætte.',
save_session: 'Gem session',
save_session_c2a: 'Opret en konto for at gemme din nuværende session og undgå at miste dit arbejde.',
scan_qr_c2a: 'Scan koden nedenfor\nfor at logge ind på denne session fra andre enheder',
scan_qr_2fa: 'Scan QR-koden med din autentifikator-app',
scan_qr_generic: 'Scan denne QR-kode med din telefon eller en anden enhed',
search: 'Søg',
seconds: 'sekunder',
security: "Sikkerhed",
select: "Vælg",
selected: 'valgt',
select_color: 'Vælg farve…',
sessions: "Sessioner",
send: "Send",
send_password_recovery_email: "Send e-mail til gendannelse af adgangskode",
session_saved: "Tak fordi du oprettede en konto. Denne session er blevet gemt.",
settings: "Indstillinger",
set_new_password: "Indstil ny adgangskode",
share: "Del",
share_to: "Del til",
share_with: "Del med:",
shortcut_to: "Genvej til",
show_all_windows: "Vis alle vinduer",
show_hidden: 'Vis skjulte',
sign_in_with_puter: "Log ind med Puter",
sign_up: "Tilmeld dig",
signing_in: "Logger ind…",
size: 'Størrelse',
skip: 'Spring over',
something_went_wrong: "Noget gik galt.",
sort_by: 'Sorter efter',
start: 'Start',
status: "Status",
storage_usage: "Lagerforbrug",
storage_puter_used: 'brugt af Puter',
taking_longer_than_usual: 'Tager lidt længere tid end normalt. Vent venligst...',
task_manager: "Opgavehåndtering",
taskmgr_header_name: "Navn",
taskmgr_header_status: "Status",
taskmgr_header_type: "Type",
terms: "Vilkår",
text_document: 'Tekstdokument',
tos_fineprint: `Ved at klikke på 'Opret gratis konto' accepterer du Puters {{link=terms}}Brugsvilkår{{/link}} og {{link=privacy}}Privatlivspolitik{{/link}}.`,
transparency: "Gennemsigtighed",
trash: 'Papirkurv',
two_factor: 'To-faktor autentifikation',
two_factor_disabled: '2FA deaktiveret',
two_factor_enabled: '2FA aktiveret',
type: 'Type',
type_confirm_to_delete_account: "Skriv 'bekræft' for at slette din konto.",
ui_colors: "UI-farver",
ui_manage_sessions: "Session Manager",
ui_revoke: "Tilbagekald",
undo: 'Fortryd',
unlimited: 'Ubegrænset',
unzip: "Udpak",
upload: 'Upload',
upload_here: 'Upload her',
usage: 'Brug',
username: "Brugernavn",
username_changed: 'Brugernavn opdateret succesfuldt.',
username_required: 'Brugernavn er påkrævet.',
versions: "Versioner",
videos: 'Videoer',
visibility: 'Synlighed',
yes: 'Ja',
yes_release_it: 'Ja, frigiv det',
you_have_been_referred_to_puter_by_a_friend: "Du er blevet henvist til Puter af en ven!",
zip: "Zip",
zipping_file: "Zipper %strong%",
name: 'Dansk',
english_name: 'Danish',
code: 'da',
dictionary: {
about: 'Om',
account: 'Konto',
account_password: 'Bekræft kontoens adgangskode',
access_granted_to: 'Adgang givet til',
add_existing_account: 'Tilføj eksisterende konto',
all_fields_required: 'Alle felter er påkrævede.',
allow: 'Tillad',
apply: 'Anvend',
ascending: 'Stigende',
associated_websites: 'Tilknyttede websteder',
auto_arrange: 'Auto Arrangere',
background: 'Baggrund',
browse: 'Gennemse',
cancel: 'Annuller',
center: 'Center',
change_desktop_background: 'Ændre skrivebordsbaggrund…',
change_email: 'Ændre e-mail',
change_language: 'Ændre sprog',
change_password: 'Ændre adgangskode',
change_ui_colors: 'Ændre UI-farver',
change_username: 'Ændre brugernavn',
close: 'Luk',
close_all_windows: 'Luk alle vinduer',
close_all_windows_confirm: 'Er du sikker på, at du vil lukke alle vinduer?',
close_all_windows_and_log_out: 'Luk vinduer og log ud',
change_always_open_with: 'Vil du altid åbne denne filtype med',
color: 'Farve',
confirm: 'Bekræft',
confirm_2fa_setup: 'Jeg har tilføjet koden til min autentifikator-app',
confirm_2fa_recovery: 'Jeg har gemt mine gendannelseskoder på et sikkert sted',
confirm_account_for_free_referral_storage_c2a: 'Opret en konto og bekræft din e-mailadresse for at modtage 1 GB gratis lagerplads. Din ven vil også få 1 GB gratis lagerplads.',
confirm_code_generic_incorrect: 'Forkert kode.',
confirm_code_generic_too_many_requests: 'For mange anmodninger. Vent et par minutter.',
confirm_code_generic_submit: 'Indsend kode',
confirm_code_generic_try_again: 'Prøv igen',
confirm_code_generic_title: 'Indtast bekræftelseskode',
confirm_code_2fa_instruction: 'Indtast den 6-cifrede kode fra din autentifikator-app.',
confirm_code_2fa_submit_btn: 'Indsend',
confirm_code_2fa_title: 'Indtast 2FA-kode',
confirm_delete_multiple_items: 'Er du sikker på, at du vil slette disse elementer permanent?',
confirm_delete_single_item: 'Vil du slette dette element permanent?',
confirm_open_apps_log_out: 'Du har åbne apps. Er du sikker på, at du vil logge ud?',
confirm_new_password: 'Bekræft ny adgangskode',
confirm_delete_user: 'Er du sikker på, at du vil slette din konto? Alle dine filer og data vil blive permanent slettet. Denne handling kan ikke fortrydes.',
confirm_delete_user_title: 'Slet konto?',
confirm_session_revoke: 'Er du sikker på, at du vil tilbagekalde denne session?',
confirm_your_email_address: 'Bekræft din e-mailadresse',
contact_us: 'Kontakt os',
contact_us_verification_required: 'Du skal have en verificeret e-mailadresse for at bruge dette.',
contain: 'Indeholde',
continue: 'Fortsæt',
copy: 'Kopier',
copy_link: 'Kopier link',
copying: 'Kopierer',
copying_file: 'Kopierer %%',
cover: 'Omslag',
create_account: 'Opret konto',
create_free_account: 'Opret gratis konto',
create_shortcut: 'Opret genvej',
credits: 'Kreditter',
current_password: 'Nuværende adgangskode',
cut: 'Klip',
clock: 'Uret',
clock_visible_hide: 'Skjul - Altid skjult',
clock_visible_show: 'Vis - Altid synlig',
clock_visible_auto: 'Auto - Standard, synlig kun i fuld skærm-tilstand.',
close_all: 'Luk alle',
created: 'Oprettet',
date_modified: 'Dato ændret',
default: 'Standard',
delete: 'Slet',
delete_account: 'Slet konto',
delete_permanently: 'Slet permanent',
deleting_file: 'Sletter %%',
deploy_as_app: 'Implementer som app',
descending: 'Faldende',
desktop: 'Skrivebord',
desktop_background_fit: 'Tilpas',
developers: 'Udviklere',
dir_published_as_website: `%strong% er blevet offentliggjort til:`,
disable_2fa: 'Deaktiver 2FA',
disable_2fa_confirm: 'Er du sikker på, at du vil deaktivere 2FA?',
disable_2fa_instructions: 'Indtast din adgangskode for at deaktivere 2FA.',
disassociate_dir: 'Fjern tilknytning til mappe',
documents: 'Dokumenter',
dont_allow: 'Tillad ikke',
download: 'Download',
download_file: 'Download fil',
downloading: 'Downloader',
email: 'E-mail',
email_change_confirmation_sent: 'En bekræftelses-e-mail er sendt til din nye e-mailadresse. Kontroller din indbakke og følg instruktionerne for at fuldføre processen.',
email_invalid: 'E-mail er ugyldig.',
email_or_username: 'E-mail eller brugernavn',
email_required: 'E-mail er påkrævet.',
empty_trash: 'Tøm papirkurv',
empty_trash_confirmation: `Er du sikker på, at du vil slette elementerne i papirkurven permanent?`,
emptying_trash: 'Tømmer papirkurv…',
enable_2fa: 'Aktivér 2FA',
end_hard: 'Afslut hårdt',
end_process_force_confirm: 'Er du sikker på, at du vil afslutte denne proces tvangsmæssigt?',
end_soft: 'Afslut blødt',
enlarged_qr_code: 'Forstørret QR-kode',
enter_password_to_confirm_delete_user: 'Indtast din adgangskode for at bekræfte sletning af konto',
error_message_is_missing: 'Fejlmeddelelse mangler.',
error_unknown_cause: 'Der opstod en ukendt fejl.',
error_uploading_files: 'Fejl ved upload af filer',
favorites: 'Favoritter',
feedback: 'Feedback',
feedback_c2a: 'Brug venligst formularen nedenfor til at sende os din feedback, kommentarer og fejlrapporter.',
feedback_sent_confirmation: 'Tak fordi du kontaktede os. Hvis du har en e-mail knyttet til din konto, vil du høre fra os så hurtigt som muligt.',
fit: 'Pas',
folder: 'Mappe',
force_quit: 'Tving afslut',
forgot_pass_c2a: 'Glemt adgangskode?',
from: 'Fra',
general: 'Generelt',
get_a_copy_of_on_puter: `Få en kopi af '%%' på Puter.com!`,
get_copy_link: 'Få kopi-link',
hide_all_windows: 'Skjul alle vinduer',
home: 'Hjem',
html_document: 'HTML-dokument',
hue: 'Farvetone',
image: 'Billede',
incorrect_password: 'Forkert adgangskode',
invite_link: 'Inviter link',
item: 'element',
items_in_trash_cannot_be_renamed: `Dette element kan ikke omdøbes, fordi det er i papirkurven. For at omdøbe dette element, skal du først trække det ud af papirkurven.`,
jpeg_image: 'JPEG-billede',
keep_in_taskbar: 'Hold i proceslinjen',
language: 'Sprog',
license: 'Licens',
lightness: 'Lysstyrke',
link_copied: 'Link kopieret',
loading: 'Indlæser',
log_in: 'Log ind',
log_into_another_account_anyway: 'Log ind på en anden konto alligevel',
log_out: 'Log ud',
looks_good: 'Ser godt ud!',
manage_sessions: 'Administrer sessioner',
menubar_style: 'Menubjælke stil',
menubar_style_desktop: 'Skrivebord',
menubar_style_system: 'System',
menubar_style_window: 'Vindue',
modified: 'Ændret',
move: 'Flyt',
moving_file: 'Flytter %%',
my_websites: 'Mine websteder',
name: 'Navn',
name_cannot_be_empty: 'Navn kan ikke være tomt.',
name_cannot_contain_double_period: "Navn kan ikke være '..' tegn.",
name_cannot_contain_period: "Navn kan ikke være '.' tegn.",
name_cannot_contain_slash: "Navn kan ikke indeholde '/' tegn.",
name_must_be_string: 'Navn kan kun være en streng.',
name_too_long: `Navn kan ikke være længere end %% tegn.`,
new: 'Ny',
new_email: 'Ny e-mail',
new_folder: 'Ny mappe',
new_password: 'Ny adgangskode',
new_username: 'Nyt brugernavn',
no: 'Nej',
no_dir_associated_with_site: 'Ingen mappe tilknyttet denne adresse.',
no_websites_published: 'Du har ikke offentliggjort nogen websteder endnu. Højreklik på en mappe for at komme i gang.',
ok: 'OK',
open: 'Åbn',
open_in_new_tab: 'Åbn i ny fane',
open_in_new_window: 'Åbn i nyt vindue',
open_with: 'Åbn med',
original_name: 'Originalt navn',
original_path: 'Original sti',
oss_code_and_content: 'Open Source Software og indhold',
password: 'Adgangskode',
password_changed: 'Adgangskode ændret.',
password_recovery_rate_limit: 'Du har nået vores rate-limit; vent venligst et par minutter. For at undgå dette i fremtiden, undgå at genindlæse siden for mange gange.',
password_recovery_token_invalid: 'Denne adgangskodegendannelsestoken er ikke længere gyldig.',
password_recovery_unknown_error: 'Der opstod en ukendt fejl. Prøv venligst igen senere.',
password_required: 'Adgangskode er påkrævet.',
password_strength_error: 'Adgangskoden skal være mindst 8 tegn lang og indeholde mindst ét stort bogstav, ét lille bogstav, ét tal og ét specialtegn.',
passwords_do_not_match: '`Ny adgangskode` og `Bekræft ny adgangskode` stemmer ikke overens.',
paste: 'Sæt ind',
paste_into_folder: 'Sæt ind i mappe',
path: 'Sti',
personalization: 'Personalisering',
pick_name_for_website: 'Vælg et navn til dit websted:',
picture: 'Billede',
pictures: 'Billeder',
plural_suffix: 's',
powered_by_puter_js: `Udviklet af {{link=docs}}Puter.js{{/link}}`,
preparing: 'Forbereder...',
preparing_for_upload: 'Forbereder til upload...',
print: 'Udskriv',
privacy: 'Privatliv',
proceed_to_login: 'Fortsæt til login',
proceed_with_account_deletion: 'Fortsæt med sletning af konto',
process_status_initializing: 'Initialiserer',
process_status_running: 'Kører',
process_type_app: 'App',
process_type_init: 'Init',
process_type_ui: 'UI',
properties: 'Egenskaber',
public: 'Offentlig',
publish: 'Offentliggør',
publish_as_website: 'Offentliggør som websted',
puter_description: `Puter er en privatlivsorienteret personlig sky til at holde alle dine filer, apps og spil på ét sikkert sted, tilgængeligt fra hvor som helst, når som helst.`,
reading_file: 'Læser %strong%',
recent: 'Seneste',
recommended: 'Anbefalet',
recover_password: 'Gendan adgangskode',
refer_friends_c2a: 'Få 1 GB for hver ven, der opretter og bekræfter en konto på Puter. Din ven vil også få 1 GB!',
refer_friends_social_media_c2a: `Få 1 GB gratis lagerplads på Puter.com!`,
refresh: 'Opdater',
release_address_confirmation: `Er du sikker på, at du vil frigive denne adresse?`,
remove_from_taskbar: 'Fjern fra proceslinje',
rename: 'Omdøb',
repeat: 'Gentag',
replace: 'Erstat',
replace_all: 'Erstat alle',
resend_confirmation_code: 'Send bekræftelseskode igen',
reset_colors: 'Nulstil farver',
restart_puter_confirm: 'Er du sikker på, at du vil genstarte Puter?',
restore: 'Gendan',
save: 'Gem',
saturation: 'Mætning',
save_account: 'Gem konto',
save_account_to_get_copy_link: 'Opret venligst en konto for at fortsætte.',
save_account_to_publish: 'Opret venligst en konto for at fortsætte.',
save_session: 'Gem session',
save_session_c2a: 'Opret en konto for at gemme din nuværende session og undgå at miste dit arbejde.',
scan_qr_c2a: 'Scan koden nedenfor\nfor at logge ind på denne session fra andre enheder',
scan_qr_2fa: 'Scan QR-koden med din autentifikator-app',
scan_qr_generic: 'Scan denne QR-kode med din telefon eller en anden enhed',
search: 'Søg',
seconds: 'sekunder',
security: 'Sikkerhed',
select: 'Vælg',
selected: 'valgt',
select_color: 'Vælg farve…',
sessions: 'Sessioner',
send: 'Send',
send_password_recovery_email: 'Send e-mail til gendannelse af adgangskode',
session_saved: 'Tak fordi du oprettede en konto. Denne session er blevet gemt.',
settings: 'Indstillinger',
set_new_password: 'Indstil ny adgangskode',
share: 'Del',
share_to: 'Del til',
share_with: 'Del med:',
shortcut_to: 'Genvej til',
show_all_windows: 'Vis alle vinduer',
show_hidden: 'Vis skjulte',
sign_in_with_puter: 'Log ind med Puter',
sign_up: 'Tilmeld dig',
signing_in: 'Logger ind…',
size: 'Størrelse',
skip: 'Spring over',
something_went_wrong: 'Noget gik galt.',
sort_by: 'Sorter efter',
start: 'Start',
status: 'Status',
storage_usage: 'Lagerforbrug',
storage_puter_used: 'brugt af Puter',
taking_longer_than_usual: 'Tager lidt længere tid end normalt. Vent venligst...',
task_manager: 'Opgavehåndtering',
taskmgr_header_name: 'Navn',
taskmgr_header_status: 'Status',
taskmgr_header_type: 'Type',
terms: 'Vilkår',
text_document: 'Tekstdokument',
tos_fineprint: `Ved at klikke på 'Opret gratis konto' accepterer du Puters {{link=terms}}Brugsvilkår{{/link}} og {{link=privacy}}Privatlivspolitik{{/link}}.`,
transparency: 'Gennemsigtighed',
trash: 'Papirkurv',
two_factor: 'To-faktor autentifikation',
two_factor_disabled: '2FA deaktiveret',
two_factor_enabled: '2FA aktiveret',
type: 'Type',
type_confirm_to_delete_account: "Skriv 'bekræft' for at slette din konto.",
ui_colors: 'UI-farver',
ui_manage_sessions: 'Session Manager',
ui_revoke: 'Tilbagekald',
undo: 'Fortryd',
unlimited: 'Ubegrænset',
unzip: 'Udpak',
upload: 'Upload',
upload_here: 'Upload her',
usage: 'Brug',
username: 'Brugernavn',
username_changed: 'Brugernavn opdateret succesfuldt.',
username_required: 'Brugernavn er påkrævet.',
versions: 'Versioner',
videos: 'Videoer',
visibility: 'Synlighed',
yes: 'Ja',
yes_release_it: 'Ja, frigiv det',
you_have_been_referred_to_puter_by_a_friend: 'Du er blevet henvist til Puter af en ven!',
zip: 'Zip',
zipping_file: 'Zipper %strong%',
// === 2FA Setup ===
setup2fa_1_step_heading: 'Åbn din autentifikator-app',
setup2fa_1_instructions: `
// === 2FA Setup ===
setup2fa_1_step_heading: 'Åbn din autentifikator-app',
setup2fa_1_instructions: `
Du kan bruge enhver autentifikator-app, der understøtter Time-based One-Time Password (TOTP) protokollen.
Der er mange at vælge imellem, men hvis du er usikker
<a target="_blank" href="https://authy.com/download">Authy</a>
er et solidt valg til Android og iOS.
`,
setup2fa_2_step_heading: 'Scan QR-koden',
setup2fa_3_step_heading: 'Indtast den 6-cifrede kode',
setup2fa_4_step_heading: 'Kopier dine gendannelseskoder',
setup2fa_4_instructions: `
setup2fa_2_step_heading: 'Scan QR-koden',
setup2fa_3_step_heading: 'Indtast den 6-cifrede kode',
setup2fa_4_step_heading: 'Kopier dine gendannelseskoder',
setup2fa_4_instructions: `
Disse gendannelseskoder er den eneste måde at adgang til din konto, hvis du mister din telefon eller ikke kan bruge din autentifikator-app.
Sørg for at opbevare dem et sikkert sted.
`,
setup2fa_5_step_heading: 'Bekræft 2FA-opsætning',
setup2fa_5_confirmation_1: 'Jeg har gemt mine gendannelseskoder på et sikkert sted',
setup2fa_5_confirmation_2: 'Jeg er klar til at aktivere 2FA',
setup2fa_5_button: 'Aktivér 2FA',
setup2fa_5_step_heading: 'Bekræft 2FA-opsætning',
setup2fa_5_confirmation_1: 'Jeg har gemt mine gendannelseskoder på et sikkert sted',
setup2fa_5_confirmation_2: 'Jeg er klar til at aktivere 2FA',
setup2fa_5_button: 'Aktivér 2FA',
// === 2FA Login ===
login2fa_otp_title: 'Indtast 2FA-kode',
login2fa_otp_instructions: 'Indtast den 6-cifrede kode fra din autentifikator-app.',
login2fa_recovery_title: 'Indtast en gendannelseskode',
login2fa_recovery_instructions: 'Indtast en af dine gendannelseskoder for at få adgang til din konto.',
login2fa_use_recovery_code: 'Brug en gendannelseskode',
login2fa_recovery_back: 'Tilbage',
login2fa_recovery_placeholder: 'XXXXXXXX',
// ----------------------------------------
// Missing translations:
// ----------------------------------------
"change": 'Ændre', // In English: "Change"
"clock_visibility": 'Udsyn af ur', // In English: "Clock Visibility"
"reading": 'Læsening %strong%', // In English: "Reading %strong%"
"writing": 'Skrivning %strong%', // In English: "Writing %strong%"
"unzipping": 'Udpakker %strong%', // In English: "Unzipping %strong%"
"sequencing": 'Sekvensering %strong%', // In English: "Sequencing %strong%"
"zipping": 'Zipping %strong% ', // In English: "Zipping %strong%", // There is no direct translation for the word but Zipping is understandable for Danes.
"Editor": 'Redaktør', // In English: "Editor"
"Viewer": 'Seer', // In English: "Viewer"
"People with access": 'Persom med adgang', // In English: "People with access"
"Share With…": 'Del med…', // In English: "Share With…"
"Owner": 'Ejer', // In English: "Owner"
"You can't share with yourself.": 'Du kan ikke dele med dig selv.', // In English: "You can't share with yourself."
"This user already has access to this item": 'Denne bruger har allerede adgang til denne genstand', // In English: "This user already has access to this item"
"billing.change_payment_method": 'Skift betalingsmetode', // In English: "Change"
"billing.cancel":'Annuller', // In English: "Cancel"
"billing.download_invoice": 'Download faktura', // In English: "Download"
"billing.payment_method": 'Betalingsmetode', // In English: "Payment Method"
"billing.payment_method_updated":'Betalingsmetode opdateret!', // In English: "Payment method updated!"
"billing.confirm_payment_method": 'Bekræft betalingsmetode', // In English: "Confirm Payment Method"
"billing.payment_history": 'Betalingshistorik', // In English: "Payment History"
"billing.refunded": 'Refunderet', // In English: "Refunded"
"billing.paid": 'Betalt', // In English: "Paid"
"billing.ok": 'OK', // In English: "OK"
"billing.resume_subscription": 'Genoptag abonnement', // In English: "Resume Subscription"
"billing.subscription_cancelled":'Dit abonnement er blevet annulleret.', // In English: "Your subscription has been canceled."
"billing.subscription_cancelled_description": 'Du vil stadig have adgang til dit abonnement indtil slutningen af denne faktureringsperiode.', // In English: "You will still have access to your subscription until the end of this billing period."
"billing.offering.free": 'Gratis', // In English: "Free"
"billing.offering.pro":'Professionel', // In English: "Professional"
"billing.offering.business": 'Erhverv', // In English: "Business"
"billing.cloud_storage": 'Cloud-lagring', // In English: "Cloud Storage"
"billing.ai_access": 'AI Adgang', // In English: "AI Access"
"billing.bandwidth": 'Båndbredde', // In English: "Bandwidth"
"billing.apps_and_games": 'Apps & Spil', // In English: "Apps & Games"
"billing.upgrade_to_pro": 'Opgrader til %strong%', // In English: "Upgrade to %strong%"
"billing.switch_to": 'Skift til %strong%', // In English: "Switch to %strong%"
"billing.payment_setup": 'Betalingsopsætning', // In English: "Payment Setup"
"billing.back": 'Tilbage', // In English: "Back"
"billing.you_are_now_subscribed_to": 'Du er nu abonneret på %strong% plan.', // In English: "You are now subscribed to %strong% tier.", //
"billing.you_are_now_subscribed_to_without_tier": 'Du er nu abonneret', // In English: "You are now subscribed"
"billing.subscription_cancellation_confirmation": 'Er du sikker på, at du vil annullere dit abonnement?', // In English: "Are you sure you want to cancel your subscription?"
"billing.subscription_setup": 'Abonnementsopsætning', // In English: "Subscription Setup"
"billing.cancel_it": 'Annuller det', // In English: "Cancel It"
"billing.keep_it": 'Behold det', // In English: "Keep It"
"billing.subscription_resumed": 'Dit %strong% abonnement er blevet genoptaget!', // In English: "Your %strong% subscription has been resumed!"
"billing.upgrade_now": 'Opgrader nu', // In English: "Upgrade Now"
"billing.upgrade": 'Opgrader', // In English: "Upgrade"
"billing.currently_on_free_plan": 'Du er i øjeblikket på gratisplanen.', // In English: "You are currently on the free plan."
"billing.download_receipt": 'Download kvittering', // In English: "Download Receipt"
"billing.subscription_check_error": 'Der opstod et problem, mens vi kontrollerede din abonnementsstatus.', // In English: "A problem occurred while checking your subscription status."
"billing.email_confirmation_needed": 'Din email er ikke blevet bekræftet. Vi sender dig en kode til bekræftelse nu.', // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
"billing.sub_cancelled_but_valid_until": 'Du har annulleret dit abonnement, og det vil automatisk skifte til gratisplanen ved slutningen af faktureringsperioden.', // In English: "You have cancelled your subscription and it will automatically switch to the free tier at the end of the billing period. You will not be charged again unless you re-subscribe."
"billing.current_plan_until_end_of_period": 'Din nuværende plan indtil slutningen af denne faktureringsperiode.', // In English: "Your current plan until the end of this billing period."
"billing.current_plan": 'Nuværende plan', // In English: "Current plan"
"billing.cancelled_subscription_tier": 'Annulleret abonnement (%%)', // In English: "Cancelled Subscription (%%)"
"billing.manage": 'Administrer', // In English: "Manage"
"billing.limited": 'Begrænset', // In English: "Limited"
"billing.expanded": 'Udvidet', // In English: "Expanded"
"billing.accelerated": 'Accelereret', // In English: "Accelerated"
"billing.enjoy_msg": 'Nyd %% af cloud-lagring plus andre fordele.', // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
// === 2FA Login ===
login2fa_otp_title: 'Indtast 2FA-kode',
login2fa_otp_instructions: 'Indtast den 6-cifrede kode fra din autentifikator-app.',
login2fa_recovery_title: 'Indtast en gendannelseskode',
login2fa_recovery_instructions: 'Indtast en af dine gendannelseskoder for at få adgang til din konto.',
login2fa_use_recovery_code: 'Brug en gendannelseskode',
login2fa_recovery_back: 'Tilbage',
login2fa_recovery_placeholder: 'XXXXXXXX',
change: 'Ændre', // In English: "Change"
clock_visibility: 'Vis ur', // In English: "Clock Visibility"
reading: 'Læse %strong%', // In English: "Reading %strong%"
writing: 'Skrive %strong%', // In English: "Writing %strong%"
unzipping: 'Udpakke %strong%', // In English: "Unzipping %strong%"
sequencing: 'Sekvensering %strong%', // In English: "Sequencing %strong%"
zipping: 'Komprimer %strong%', // In English: "Zipping %strong%"
Editor: 'Redaktør', // In English: "Editor"
Viewer: 'Seer', // In English: "Viewer"
'People with access': 'Personer med adgang', // In English: "People with access"
'Share With…': 'Del med...', // In English: "Share With…"
Owner: 'Ejer', // In English: "Owner"
"You can't share with yourself.": 'Du kan ikke dele med dig selv.', // In English: "You can't share with yourself."
'This user already has access to this item': 'Denne bruger har allerede adgang til dette', // In English: "This user already has access to this item"
'billing.change_payment_method': 'Ændre', // In English: "Change"
'billing.cancel': 'Annuller', // In English: "Cancel"
'billing.download_invoice': 'Download', // In English: "Download"
'billing.payment_method': 'Betalingsmetode', // In English: "Payment Method"
'billing.payment_method_updated': 'Betalingsmetode opdateret!', // In English: "Payment method updated!"
'billing.confirm_payment_method': 'Bekræft betalingsmetode', // In English: "Confirm Payment Method"
'billing.payment_history': 'Betalings historik', // In English: "Payment History"
'billing.refunded': 'Refunderet', // In English: "Refunded"
'billing.paid': 'Betalt', // In English: "Paid"
'billing.ok': 'OK', // In English: "OK"
'billing.resume_subscription': 'Genoptag abonnement', // In English: "Resume Subscription"
'billing.subscription_cancelled': 'Dit abonnement er blevet annulleret.', // In English: "Your subscription has been canceled."
'billing.subscription_cancelled_description': 'Du vil stadigvæk have adgang til dit abonnement, indtil udgangen af denne betalingsperiode.', // In English: "You will still have access to your subscription until the end of this billing period."
'billing.offering.free': 'Gratis', // In English: "Free"
'billing.offering.pro': 'Professionel', // In English: "Professional"
'billing.offering.business': 'Virksomhed', // In English: "Business"
'billing.cloud_storage': 'Opbevaring i skyen', // In English: "Cloud Storage"
'billing.ai_access': 'AI Adgang', // In English: "AI Access"
'billing.bandwidth': 'Båndbredde', // In English: "Bandwidth"
'billing.apps_and_games': 'Apps & Spil', // In English: "Apps & Games"
'billing.upgrade_to_pro': 'Opgrader til %strong%', // In English: "Upgrade to %strong%"
'billing.switch_to': 'Skift til %strong%', // In English: "Switch to %strong%"
'billing.payment_setup': 'Betalingsopsætning', // In English: "Payment Setup"
'billing.back': 'Tilbage', // In English: "Back"
'billing.you_are_now_subscribed_to': 'Du abonnerer nu på niveauet %strong%.', // In English: "You are now subscribed to %strong% tier."
'billing.you_are_now_subscribed_to_without_tier': 'Du er nu tilmeldt', // In English: "You are now subscribed"
'billing.subscription_cancellation_confirmation': 'Er du sikker på at du vil opsige dit abonnement?', // In English: "Are you sure you want to cancel your subscription?"
'billing.subscription_setup': 'Abonnementsopsætning', // In English: "Subscription Setup"
'billing.cancel_it': 'Annuler det', // In English: "Cancel It"
'billing.keep_it': 'Behold det', // In English: "Keep It"
'billing.subscription_resumed': 'Dit %strong% abonnement er genoptaget!', // In English: "Your %strong% subscription has been resumed!"
'billing.upgrade_now': 'Opgrader nu', // In English: "Upgrade Now"
'billing.upgrade': 'Opgrader', // In English: "Upgrade"
'billing.currently_on_free_plan': 'Du er i øjeblikket på det gratis plan.', // In English: "You are currently on the free plan."
'billing.download_receipt': 'Download kvittering', // In English: "Download Receipt"
'billing.subscription_check_error': 'Der opstod et problem under kontrol af din abonnementsstatus', // In English: "A problem occurred while checking your subscription status."
'billing.email_confirmation_needed': 'Din e-mail er ikke blevet bekræftet. Vi sender dig en kode nu for at bekræfte den.', // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
'billing.sub_cancelled_but_valid_until':
'Du har opsagt dit abonnement og vil automatisk blive skiftet til den gratis plan i slutningen af denne betalingsperiode. Der vil ikke blive trukket flere penge fra din konto, medmindre du genoptager dit abonnement.', // In English: "You have cancelled your subscription and it will automatically switch to the free tier at the end of the billing period. You will not be charged again unless you re-subscribe."
'billing.current_plan_until_end_of_period': 'Din nuværende plan, indtil slutningen af denne betalingsperiode.', // In English: "Your current plan until the end of this billing period."
'billing.current_plan': 'Nuværende plan', // In English: "Current plan"
'billing.cancelled_subscription_tier': 'Opsagt abonnement (%%)', // In English: "Cancelled Subscription (%%)"
'billing.manage': 'Styre', // In English: "Manage"
'billing.limited': 'Begrænset', // In English: "Limited"
'billing.expanded': 'Udvidet', // In English: "Expanded"
'billing.accelerated': 'Accelereret', // In English: "Accelerated"
'billing.enjoy_msg': 'Nyd %% af lagringsplads i skyen og andre fordele. ', // In English: "Enjoy %% of Cloud Storage plus other benefits."
},
};
export default da;