From 4e47c8bf9dca82b89c7d6509a4479986fff2c456 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Thu, 12 Dec 2024 13:02:22 -0500 Subject: [PATCH 1/3] clean: cleanup utils directory --- src/backend/src/util/context.js | 2 -- src/backend/src/util/fuzz.js | 50 ------------------------------ src/backend/src/util/lockutil.js | 8 ++--- src/backend/src/util/otelutil.js | 1 - src/backend/src/util/queuing.js | 21 ------------- src/backend/src/util/retryutil.js | 2 +- src/backend/src/util/streamutil.js | 28 +++++++---------- src/backend/src/util/validutil.js | 2 +- src/backend/src/util/workutil.js | 3 +- 9 files changed, 18 insertions(+), 99 deletions(-) delete mode 100644 src/backend/src/util/queuing.js 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); } From 651076a39bf15c7eb42ff5dfae7dea61ba9e8997 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Thu, 12 Dec 2024 13:47:03 -0500 Subject: [PATCH 2/3] doc: document permission scanning --- .../src/services/auth/PermissionService.js | 73 ++++++++++++++----- .../src/services/auth/VirtualGroupService.js | 8 ++ .../src/unstructured/permission-scan-lib.js | 11 +-- .../src/unstructured/permission-scanners.js | 47 +++++++++++- 4 files changed, 110 insertions(+), 29 deletions(-) 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) ) { From 51265712173b19688fe8ce7d8ec424e4e8b087a0 Mon Sep 17 00:00:00 2001 From: lauritsbonde Date: Thu, 12 Dec 2024 19:48:34 +0100 Subject: [PATCH 3/3] add missing danish translations --- src/gui/src/i18n/translations/da.js | 765 ++++++++++++++-------------- 1 file changed, 381 insertions(+), 384 deletions(-) diff --git a/src/gui/src/i18n/translations/da.js b/src/gui/src/i18n/translations/da.js index 09fb08b3..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": undefined, // In English: "Change" - "clock_visibility": undefined, // In English: "Clock Visibility" - "reading": undefined, // In English: "Reading %strong%" - "writing": undefined, // In English: "Writing %strong%" - "unzipping": undefined, // In English: "Unzipping %strong%" - "sequencing": undefined, // In English: "Sequencing %strong%" - "zipping": undefined, // In English: "Zipping %strong%" - "Editor": undefined, // In English: "Editor" - "Viewer": undefined, // In English: "Viewer" - "People with access": undefined, // In English: "People with access" - "Share With…": undefined, // In English: "Share With…" - "Owner": undefined, // In English: "Owner" - "You can't share with yourself.": undefined, // In English: "You can't share with yourself." - "This user already has access to this item": undefined, // In English: "This user already has access to this item" - "billing.change_payment_method": undefined, // In English: "Change" - "billing.cancel": undefined, // In English: "Cancel" - "billing.download_invoice": undefined, // In English: "Download" - "billing.payment_method": undefined, // In English: "Payment Method" - "billing.payment_method_updated": undefined, // In English: "Payment method updated!" - "billing.confirm_payment_method": undefined, // In English: "Confirm Payment Method" - "billing.payment_history": undefined, // In English: "Payment History" - "billing.refunded": undefined, // In English: "Refunded" - "billing.paid": undefined, // In English: "Paid" - "billing.ok": undefined, // In English: "OK" - "billing.resume_subscription": undefined, // In English: "Resume Subscription" - "billing.subscription_cancelled": undefined, // In English: "Your subscription has been canceled." - "billing.subscription_cancelled_description": undefined, // In English: "You will still have access to your subscription until the end of this billing period." - "billing.offering.free": undefined, // In English: "Free" - "billing.offering.pro": undefined, // In English: "Professional" - "billing.offering.business": undefined, // In English: "Business" - "billing.cloud_storage": undefined, // In English: "Cloud Storage" - "billing.ai_access": undefined, // In English: "AI Access" - "billing.bandwidth": undefined, // In English: "Bandwidth" - "billing.apps_and_games": undefined, // In English: "Apps & Games" - "billing.upgrade_to_pro": undefined, // In English: "Upgrade to %strong%" - "billing.switch_to": undefined, // In English: "Switch to %strong%" - "billing.payment_setup": undefined, // In English: "Payment Setup" - "billing.back": undefined, // In English: "Back" - "billing.you_are_now_subscribed_to": undefined, // In English: "You are now subscribed to %strong% tier." - "billing.you_are_now_subscribed_to_without_tier": undefined, // In English: "You are now subscribed" - "billing.subscription_cancellation_confirmation": undefined, // In English: "Are you sure you want to cancel your subscription?" - "billing.subscription_setup": undefined, // In English: "Subscription Setup" - "billing.cancel_it": undefined, // In English: "Cancel It" - "billing.keep_it": undefined, // In English: "Keep It" - "billing.subscription_resumed": undefined, // In English: "Your %strong% subscription has been resumed!" - "billing.upgrade_now": undefined, // In English: "Upgrade Now" - "billing.upgrade": undefined, // In English: "Upgrade" - "billing.currently_on_free_plan": undefined, // In English: "You are currently on the free plan." - "billing.download_receipt": undefined, // In English: "Download Receipt" - "billing.subscription_check_error": undefined, // In English: "A problem occurred while checking your subscription status." - "billing.email_confirmation_needed": undefined, // In English: "Your email has not been confirmed. We'll send you a code to confirm it now." - "billing.sub_cancelled_but_valid_until": undefined, // 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": undefined, // In English: "Your current plan until the end of this billing period." - "billing.current_plan": undefined, // In English: "Current plan" - "billing.cancelled_subscription_tier": undefined, // In English: "Cancelled Subscription (%%)" - "billing.manage": undefined, // In English: "Manage" - "billing.limited": undefined, // In English: "Limited" - "billing.expanded": undefined, // In English: "Expanded" - "billing.accelerated": undefined, // In English: "Accelerated" - "billing.enjoy_msg": undefined, // 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;