diff --git a/src/backend/src/services/auth/PermissionService.js b/src/backend/src/services/auth/PermissionService.js
index d652060c..487101bd 100644
--- a/src/backend/src/services/auth/PermissionService.js
+++ b/src/backend/src/services/auth/PermissionService.js
@@ -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');
diff --git a/src/backend/src/services/auth/VirtualGroupService.js b/src/backend/src/services/auth/VirtualGroupService.js
index 3e0d36b7..607144f6 100644
--- a/src/backend/src/services/auth/VirtualGroupService.js
+++ b/src/backend/src/services/auth/VirtualGroupService.js
@@ -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);
}
diff --git a/src/backend/src/unstructured/permission-scan-lib.js b/src/backend/src/unstructured/permission-scan-lib.js
index 6a266b1d..2f333130 100644
--- a/src/backend/src/unstructured/permission-scan-lib.js
+++ b/src/backend/src/unstructured/permission-scan-lib.js
@@ -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;
}
diff --git a/src/backend/src/unstructured/permission-scanners.js b/src/backend/src/unstructured/permission-scanners.js
index 124ef348..3bf20863 100644
--- a/src/backend/src/unstructured/permission-scanners.js
+++ b/src/backend/src/unstructured/permission-scanners.js
@@ -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) ) {
diff --git a/src/backend/src/util/context.js b/src/backend/src/util/context.js
index f7a2ffcb..15fdb941 100644
--- a/src/backend/src/util/context.js
+++ b/src/backend/src/util/context.js
@@ -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);
diff --git a/src/backend/src/util/fuzz.js b/src/backend/src/util/fuzz.js
index b7676378..56e12002 100644
--- a/src/backend/src/util/fuzz.js
+++ b/src/backend/src/util/fuzz.js
@@ -16,15 +16,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-// 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
};
diff --git a/src/backend/src/util/lockutil.js b/src/backend/src/util/lockutil.js
index dde83572..60c8b1a8 100644
--- a/src/backend/src/util/lockutil.js
+++ b/src/backend/src/util/lockutil.js
@@ -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_();
diff --git a/src/backend/src/util/otelutil.js b/src/backend/src/util/otelutil.js
index e8aef304..431d820f 100644
--- a/src/backend/src/util/otelutil.js
+++ b/src/backend/src/util/otelutil.js
@@ -100,7 +100,6 @@ class ParallelTasks {
return;
}
- // const span = this.tracer.startSpan(name);
this.promises.push(this.run_(name, fn));
}
diff --git a/src/backend/src/util/queuing.js b/src/backend/src/util/queuing.js
deleted file mode 100644
index 47955c28..00000000
--- a/src/backend/src/util/queuing.js
+++ /dev/null
@@ -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 .
- */
-class QueueBatcher {
- //
-}
diff --git a/src/backend/src/util/retryutil.js b/src/backend/src/util/retryutil.js
index 0bdc614f..dfb4ae0d 100644
--- a/src/backend/src/util/retryutil.js
+++ b/src/backend/src/util/retryutil.js
@@ -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();
diff --git a/src/backend/src/util/streamutil.js b/src/backend/src/util/streamutil.js
index bd06b7af..d095b342 100644
--- a/src/backend/src/util/streamutil.js
+++ b/src/backend/src/util/streamutil.js
@@ -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');
}
}
diff --git a/src/backend/src/util/validutil.js b/src/backend/src/util/validutil.js
index cb4a5f25..fe64fdda 100644
--- a/src/backend/src/util/validutil.js
+++ b/src/backend/src/util/validutil.js
@@ -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 };
diff --git a/src/backend/src/util/workutil.js b/src/backend/src/util/workutil.js
index 644a4b97..d1d86092 100644
--- a/src/backend/src/util/workutil.js
+++ b/src/backend/src/util/workutil.js
@@ -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);
}
diff --git a/src/gui/src/i18n/translations/da.js b/src/gui/src/i18n/translations/da.js
index 68221e38..a68fbd73 100644
--- a/src/gui/src/i18n/translations/da.js
+++ b/src/gui/src/i18n/translations/da.js
@@ -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%",
-
- // === 2FA Setup ===
- setup2fa_1_step_heading: 'Åbn din autentifikator-app',
- setup2fa_1_instructions: `
+ 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: `
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
Authy
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 få adgang til din konto, hvis du mister din telefon eller ikke kan bruge din autentifikator-app.
Sørg for at opbevare dem på 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',
-
- // === 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',
+ 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',
- // ----------------------------------------
- // 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;