mirror of
https://github.com/HeyPuter/puter.git
synced 2025-01-23 22:40:20 +08:00
Merge branch 'main' into #875
This commit is contained in:
commit
a879c83369
@ -37,10 +37,16 @@ const implicit_user_permissions = {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PermissionService class manages the core functionality for handling permissions within the Puter ecosystem.
|
* Permission rewriters are used to map one set of permission strings to another.
|
||||||
* It provides methods for granting, revoking, and checking permissions for various entities such as users and applications.
|
* These are invoked during permission scanning and when permissions are granted or revoked.
|
||||||
* 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.
|
*
|
||||||
*/
|
* 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 {
|
class PermissionRewriter {
|
||||||
static create ({ id, matcher, rewriter }) {
|
static create ({ id, matcher, rewriter }) {
|
||||||
return new PermissionRewriter({ id, matcher, rewriter });
|
return new PermissionRewriter({ id, matcher, rewriter });
|
||||||
@ -70,11 +76,17 @@ class PermissionRewriter {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PermissionImplicator class is used to manage implicit permissions.
|
* Permission implicators are used to manage implicit permissions.
|
||||||
* It defines methods to match and check if a given permission is implicitly granted to an actor.
|
* It defines a method to check if a given permission is implicitly granted to an actor.
|
||||||
* @class
|
*
|
||||||
* @name PermissionImplicator
|
* 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 {
|
class PermissionImplicator {
|
||||||
static create ({ id, matcher, checker }) {
|
static create ({ id, matcher, checker }) {
|
||||||
return new PermissionImplicator({ 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.
|
* Permission exploders are used to map any permission to a list of permissions
|
||||||
* It uses a matcher function to determine if a permission should be exploded and an exploder function to perform the expansion.
|
* which are considered to imply the specified permission.
|
||||||
* This class is part of the permission management system, allowing for dynamic and complex permission structures.
|
*
|
||||||
*
|
* It uses a matcher function to determine if a permission should be exploded
|
||||||
* @class PermissionExploder
|
* 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 {
|
class PermissionExploder {
|
||||||
static create ({ id, matcher, exploder }) {
|
static create ({ id, matcher, exploder }) {
|
||||||
return new PermissionExploder({ id, matcher, exploder });
|
return new PermissionExploder({ id, matcher, exploder });
|
||||||
@ -952,14 +969,26 @@ class PermissionService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
register_rewriter (translator) {
|
/**
|
||||||
if ( ! (translator instanceof PermissionRewriter) ) {
|
* Register a permission rewriter. For details see the documentation on the
|
||||||
throw new Error('translator must be a PermissionRewriter');
|
* 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) {
|
register_implicator (implicator) {
|
||||||
if ( ! (implicator instanceof PermissionImplicator) ) {
|
if ( ! (implicator instanceof PermissionImplicator) ) {
|
||||||
throw new Error('implicator must be a PermissionImplicator');
|
throw new Error('implicator must be a PermissionImplicator');
|
||||||
@ -968,6 +997,12 @@ class PermissionService extends BaseService {
|
|||||||
this._permission_implicators.push(implicator);
|
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) {
|
register_exploder (exploder) {
|
||||||
if ( ! (exploder instanceof PermissionExploder) ) {
|
if ( ! (exploder instanceof PermissionExploder) ) {
|
||||||
throw new Error('exploder must be a PermissionExploder');
|
throw new Error('exploder must be a PermissionExploder');
|
||||||
|
@ -21,6 +21,14 @@ class VirtualGroupService extends BaseService {
|
|||||||
this.membership_implicators_ = [];
|
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) {
|
register_membership_implicator (implicator) {
|
||||||
this.membership_implicators_.push(implicator);
|
this.membership_implicators_.push(implicator);
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,7 @@
|
|||||||
const remove_paths_through_user = ({ reading, user }) => {
|
const remove_paths_through_user = ({ reading, user }) => {
|
||||||
const no_cycle_reading = [];
|
const no_cycle_reading = [];
|
||||||
|
|
||||||
for ( let i = 0 ; i < reading.length ; i++ ) {
|
for ( const node of reading ) {
|
||||||
const node = reading[i];
|
|
||||||
|
|
||||||
console.log('checking node...', node);
|
|
||||||
|
|
||||||
if ( node.$ === 'path' ) {
|
if ( node.$ === 'path' ) {
|
||||||
if (
|
if (
|
||||||
node.issuer_username === user.username
|
node.issuer_username === user.username
|
||||||
@ -33,14 +29,11 @@ const remove_paths_through_user = ({ reading, user }) => {
|
|||||||
no_cycle_reading.push(node);
|
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;
|
return no_cycle_reading;
|
||||||
};
|
};
|
||||||
|
|
||||||
const reading_has_terminal = ({ reading }) => {
|
const reading_has_terminal = ({ reading }) => {
|
||||||
for ( let i = 0 ; i < reading.length ; i++ ) {
|
for ( const node of reading ) {
|
||||||
const node = reading[i];
|
|
||||||
if ( node.has_terminal ) {
|
if ( node.has_terminal ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,26 @@ const { reading_has_terminal } = require("./permission-scan-lib");
|
|||||||
"Ctrl+K, Ctrl+J" or "⌘K, ⌘J";
|
"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 = [
|
const PERMISSION_SCANNERS = [
|
||||||
{
|
{
|
||||||
name: 'implied',
|
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) {
|
async scan (a) {
|
||||||
const reading = a.get('reading');
|
const reading = a.get('reading');
|
||||||
const { actor, permission_options } = a.values();
|
const { actor, permission_options } = a.values();
|
||||||
@ -46,6 +63,9 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-user',
|
name: 'user-user',
|
||||||
|
documentation: `
|
||||||
|
User-to-User permissions are permission granted form one user to another.
|
||||||
|
`,
|
||||||
async scan (a) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options, state } = a.values();
|
const { reading, actor, permission_options, state } = a.values();
|
||||||
if ( !(actor.type instanceof UserActorType) ) {
|
if ( !(actor.type instanceof UserActorType) ) {
|
||||||
@ -96,7 +116,6 @@ const PERMISSION_SCANNERS = [
|
|||||||
|
|
||||||
if ( should_continue ) continue;
|
if ( should_continue ) continue;
|
||||||
|
|
||||||
// const issuer_perm = await this.check(issuer_actor, row.permission);
|
|
||||||
const issuer_reading = await a.icall(
|
const issuer_reading = await a.icall(
|
||||||
'scan', issuer_actor, row.permission, undefined, state);
|
'scan', issuer_actor, row.permission, undefined, state);
|
||||||
|
|
||||||
@ -117,6 +136,13 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'hc-user-group-user',
|
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) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
if ( !(actor.type instanceof UserActorType) ) {
|
if ( !(actor.type instanceof UserActorType) ) {
|
||||||
@ -167,6 +193,11 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-group-user',
|
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) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
if ( !(actor.type instanceof UserActorType) ) {
|
if ( !(actor.type instanceof UserActorType) ) {
|
||||||
@ -223,6 +254,16 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-virtual-group-user',
|
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) {
|
async scan (a) {
|
||||||
const svc_virtualGroup = await a.iget('services').get('virtual-group');
|
const svc_virtualGroup = await a.iget('services').get('virtual-group');
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
@ -248,6 +289,10 @@ const PERMISSION_SCANNERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-app',
|
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) {
|
async scan (a) {
|
||||||
const { reading, actor, permission_options } = a.values();
|
const { reading, actor, permission_options } = a.values();
|
||||||
if ( !(actor.type instanceof AppUnderUserActorType) ) {
|
if ( !(actor.type instanceof AppUnderUserActorType) ) {
|
||||||
|
@ -52,7 +52,6 @@ class Context {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// x = globalThis.root_context ?? this.create({});
|
|
||||||
x = this.root.sub({}, this.USE_NAME_FALLBACK);
|
x = this.root.sub({}, this.USE_NAME_FALLBACK);
|
||||||
}
|
}
|
||||||
if ( x && k ) return x.get(k);
|
if ( x && k ) return x.get(k);
|
||||||
@ -180,7 +179,6 @@ class Context {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
abind (cb) {
|
abind (cb) {
|
||||||
const als = this.constructor.contextAsyncLocalStorage;
|
|
||||||
return async (...args) => {
|
return async (...args) => {
|
||||||
return await this.arun(async () => {
|
return await this.arun(async () => {
|
||||||
return await cb(...args);
|
return await cb(...args);
|
||||||
|
@ -16,15 +16,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
// function fuzz_number(n) {
|
|
||||||
// if (n === 0) return 0;
|
|
||||||
|
|
||||||
// // let randomized = n + (Math.random() - 0.5) * n * 0.2;
|
|
||||||
// let randomized = n;
|
|
||||||
// let magnitude = Math.floor(Math.log10(randomized));
|
|
||||||
// let factor = Math.pow(10, magnitude);
|
|
||||||
// return Math.round(randomized / factor) * factor;
|
|
||||||
// }
|
|
||||||
|
|
||||||
function fuzz_number(num) {
|
function fuzz_number(num) {
|
||||||
// If the number is 0, then return 0
|
// If the number is 0, then return 0
|
||||||
@ -46,47 +37,6 @@ function fuzz_number(num) {
|
|||||||
return Math.round(num / factor) * factor;
|
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 = {
|
module.exports = {
|
||||||
fuzz_number
|
fuzz_number
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
const { TeePromise } = require('@heyputer/putility').libs.promise;
|
const { TeePromise } = require('@heyputer/putility').libs.promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RWLock is a read-write lock that allows multiple readers or a single writer.
|
||||||
|
*/
|
||||||
class RWLock {
|
class RWLock {
|
||||||
static TYPE_READ = Symbol('read');
|
static TYPE_READ = Symbol('read');
|
||||||
static TYPE_WRITE = Symbol('write');
|
static TYPE_WRITE = Symbol('write');
|
||||||
@ -45,11 +48,6 @@ class RWLock {
|
|||||||
this.check_queue_();
|
this.check_queue_();
|
||||||
}
|
}
|
||||||
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.queue.length === 0 ) {
|
||||||
if ( this.readers_ === 0 && ! this.writer_ ) {
|
if ( this.readers_ === 0 && ! this.writer_ ) {
|
||||||
this.on_empty_();
|
this.on_empty_();
|
||||||
|
@ -100,7 +100,6 @@ class ParallelTasks {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const span = this.tracer.startSpan(name);
|
|
||||||
this.promises.push(this.run_(name, fn));
|
this.promises.push(this.run_(name, fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Puter Technologies Inc.
|
|
||||||
*
|
|
||||||
* This file is part of Puter.
|
|
||||||
*
|
|
||||||
* Puter is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published
|
|
||||||
* by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
class QueueBatcher {
|
|
||||||
//
|
|
||||||
}
|
|
@ -52,7 +52,7 @@ const simple_retry = async function simple_retry (func, max_tries, interval) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const poll = async function poll({ poll_fn, schedule_fn }) {
|
const poll = async function poll({ poll_fn, schedule_fn }) {
|
||||||
let delay = undefined;
|
let delay;
|
||||||
|
|
||||||
while ( true ) {
|
while ( true ) {
|
||||||
const is_done = await poll_fn();
|
const is_done = await poll_fn();
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
const { PassThrough, Readable, Transform } = require('stream');
|
const { PassThrough, Readable, Transform } = require('stream');
|
||||||
const { TeePromise } = require('@heyputer/putility').libs.promise;
|
const { TeePromise } = require('@heyputer/putility').libs.promise;
|
||||||
const { EWMA } = require('./opmath');
|
|
||||||
|
|
||||||
class StreamBuffer extends TeePromise {
|
class StreamBuffer extends TeePromise {
|
||||||
constructor () {
|
constructor () {
|
||||||
@ -47,6 +46,15 @@ const stream_to_the_void = stream => {
|
|||||||
stream.on('error', () => {});
|
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 pausing_tee = (source, n) => {
|
||||||
const { PassThrough } = require('stream');
|
const { PassThrough } = require('stream');
|
||||||
|
|
||||||
@ -59,39 +67,31 @@ const pausing_tee = (source, n) => {
|
|||||||
streams_.push(stream);
|
streams_.push(stream);
|
||||||
stream.on('drain', () => {
|
stream.on('drain', () => {
|
||||||
ready_[i] = true;
|
ready_[i] = true;
|
||||||
// console.log(source.id, 'PR :: drain from reader', i, ready_);
|
|
||||||
if ( first_ ) {
|
if ( first_ ) {
|
||||||
source.resume();
|
source.resume();
|
||||||
first_ = false;
|
first_ = false;
|
||||||
}
|
}
|
||||||
if (ready_.every(v => !! v)) source.resume();
|
if (ready_.every(v => !! v)) source.resume();
|
||||||
});
|
});
|
||||||
// stream.on('newListener', (event, listener) => {
|
|
||||||
// console.log('PR :: newListener', i, event, listener);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source.on('data', (chunk) => {
|
source.on('data', (chunk) => {
|
||||||
// console.log(source.id, 'PT :: data from source', chunk.length);
|
|
||||||
ready_.forEach((v, i) => {
|
ready_.forEach((v, i) => {
|
||||||
ready_[i] = streams_[i].write(chunk);
|
ready_[i] = streams_[i].write(chunk);
|
||||||
});
|
});
|
||||||
if ( ! ready_.every(v => !! v) ) {
|
if ( ! ready_.every(v => !! v) ) {
|
||||||
// console.log('PT :: pausing source', ready_);
|
|
||||||
source.pause();
|
source.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
source.on('end', () => {
|
source.on('end', () => {
|
||||||
// console.log(source.id, 'PT :: end from source');
|
|
||||||
for ( let i=0 ; i < n ; i++ ) {
|
for ( let i=0 ; i < n ; i++ ) {
|
||||||
streams_[i].end();
|
streams_[i].end();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
source.on('error', (err) => {
|
source.on('error', (err) => {
|
||||||
// console.log(source.id, 'PT :: error from source', err);
|
|
||||||
for ( let i=0 ; i < n ; i++ ) {
|
for ( let i=0 ; i < n ; i++ ) {
|
||||||
streams_[i].emit('error', err);
|
streams_[i].emit('error', err);
|
||||||
}
|
}
|
||||||
@ -100,6 +100,9 @@ const pausing_tee = (source, n) => {
|
|||||||
return streams_;
|
return streams_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A debugging stream transform that logs the data it receives.
|
||||||
|
*/
|
||||||
class LoggingStream extends Transform {
|
class LoggingStream extends Transform {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
@ -431,9 +434,7 @@ async function* chunk_stream(
|
|||||||
offset += amount;
|
offset += amount;
|
||||||
|
|
||||||
while (offset >= chunk_size) {
|
while (offset >= chunk_size) {
|
||||||
console.log('start yield');
|
|
||||||
yield buffer;
|
yield buffer;
|
||||||
console.log('end yield');
|
|
||||||
|
|
||||||
buffer = Buffer.alloc(chunk_size);
|
buffer = Buffer.alloc(chunk_size);
|
||||||
offset = 0;
|
offset = 0;
|
||||||
@ -449,13 +450,8 @@ async function* chunk_stream(
|
|||||||
|
|
||||||
if ( chunk_time_ewma !== null ) {
|
if ( chunk_time_ewma !== null ) {
|
||||||
const chunk_time = chunk_time_ewma.get();
|
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 = (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));
|
await new Promise(resolve => setTimeout(resolve, sleep_time));
|
||||||
console.log('end sleep');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ const valid_file_size = v => {
|
|||||||
if ( ! Number.isInteger(v) ) {
|
if ( ! Number.isInteger(v) ) {
|
||||||
return { ok: false, v };
|
return { ok: false, v };
|
||||||
}
|
}
|
||||||
if ( ! (v >= 0) ) {
|
if ( v < 0 ) {
|
||||||
return { ok: false, v };
|
return { ok: false, v };
|
||||||
}
|
}
|
||||||
return { ok: true, v };
|
return { ok: true, v };
|
||||||
|
@ -28,8 +28,7 @@ class WorkList {
|
|||||||
|
|
||||||
clear_invalid () {
|
clear_invalid () {
|
||||||
const new_items = [];
|
const new_items = [];
|
||||||
for ( let i=0 ; i < this.items.length ; i++ ) {
|
for ( const item of this.items ) {
|
||||||
const item = this.items[i];
|
|
||||||
if ( item.invalid ) continue;
|
if ( item.invalid ) continue;
|
||||||
new_items.push(item);
|
new_items.push(item);
|
||||||
}
|
}
|
||||||
|
@ -18,399 +18,396 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const da = {
|
const da = {
|
||||||
name: "Dansk",
|
name: 'Dansk',
|
||||||
english_name: "Danish",
|
english_name: 'Danish',
|
||||||
code: "da",
|
code: 'da',
|
||||||
dictionary: {
|
dictionary: {
|
||||||
about: "Om",
|
about: 'Om',
|
||||||
account: "Konto",
|
account: 'Konto',
|
||||||
account_password: "Bekræft kontoens adgangskode",
|
account_password: 'Bekræft kontoens adgangskode',
|
||||||
access_granted_to: "Adgang givet til",
|
access_granted_to: 'Adgang givet til',
|
||||||
add_existing_account: "Tilføj eksisterende konto",
|
add_existing_account: 'Tilføj eksisterende konto',
|
||||||
all_fields_required: 'Alle felter er påkrævede.',
|
all_fields_required: 'Alle felter er påkrævede.',
|
||||||
allow: 'Tillad',
|
allow: 'Tillad',
|
||||||
apply: "Anvend",
|
apply: 'Anvend',
|
||||||
ascending: 'Stigende',
|
ascending: 'Stigende',
|
||||||
associated_websites: "Tilknyttede websteder",
|
associated_websites: 'Tilknyttede websteder',
|
||||||
auto_arrange: 'Auto Arrangere',
|
auto_arrange: 'Auto Arrangere',
|
||||||
background: "Baggrund",
|
background: 'Baggrund',
|
||||||
browse: "Gennemse",
|
browse: 'Gennemse',
|
||||||
cancel: 'Annuller',
|
cancel: 'Annuller',
|
||||||
center: 'Center',
|
center: 'Center',
|
||||||
change_desktop_background: 'Ændre skrivebordsbaggrund…',
|
change_desktop_background: 'Ændre skrivebordsbaggrund…',
|
||||||
change_email: "Ændre e-mail",
|
change_email: 'Ændre e-mail',
|
||||||
change_language: "Ændre sprog",
|
change_language: 'Ændre sprog',
|
||||||
change_password: "Ændre adgangskode",
|
change_password: 'Ændre adgangskode',
|
||||||
change_ui_colors: "Ændre UI-farver",
|
change_ui_colors: 'Ændre UI-farver',
|
||||||
change_username: "Ændre brugernavn",
|
change_username: 'Ændre brugernavn',
|
||||||
close: 'Luk',
|
close: 'Luk',
|
||||||
close_all_windows: "Luk alle vinduer",
|
close_all_windows: 'Luk alle vinduer',
|
||||||
close_all_windows_confirm: "Er du sikker på, at du vil lukke 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',
|
close_all_windows_and_log_out: 'Luk vinduer og log ud',
|
||||||
change_always_open_with: "Vil du altid åbne denne filtype med",
|
change_always_open_with: 'Vil du altid åbne denne filtype med',
|
||||||
color: 'Farve',
|
color: 'Farve',
|
||||||
confirm: 'Bekræft',
|
confirm: 'Bekræft',
|
||||||
confirm_2fa_setup: 'Jeg har tilføjet koden til min autentifikator-app',
|
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_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_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_incorrect: 'Forkert kode.',
|
||||||
confirm_code_generic_too_many_requests: "For mange anmodninger. Vent et par minutter.",
|
confirm_code_generic_too_many_requests: 'For mange anmodninger. Vent et par minutter.',
|
||||||
confirm_code_generic_submit: "Indsend kode",
|
confirm_code_generic_submit: 'Indsend kode',
|
||||||
confirm_code_generic_try_again: "Prøv igen",
|
confirm_code_generic_try_again: 'Prøv igen',
|
||||||
confirm_code_generic_title: "Indtast bekræftelseskode",
|
confirm_code_generic_title: 'Indtast bekræftelseskode',
|
||||||
confirm_code_2fa_instruction: "Indtast den 6-cifrede kode fra din autentifikator-app.",
|
confirm_code_2fa_instruction: 'Indtast den 6-cifrede kode fra din autentifikator-app.',
|
||||||
confirm_code_2fa_submit_btn: "Indsend",
|
confirm_code_2fa_submit_btn: 'Indsend',
|
||||||
confirm_code_2fa_title: "Indtast 2FA-kode",
|
confirm_code_2fa_title: 'Indtast 2FA-kode',
|
||||||
confirm_delete_multiple_items: 'Er du sikker på, at du vil slette disse elementer permanent?',
|
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_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_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_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: '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_delete_user_title: 'Slet konto?',
|
||||||
confirm_session_revoke: "Er du sikker på, at du vil tilbagekalde denne session?",
|
confirm_session_revoke: 'Er du sikker på, at du vil tilbagekalde denne session?',
|
||||||
confirm_your_email_address: "Bekræft din e-mailadresse",
|
confirm_your_email_address: 'Bekræft din e-mailadresse',
|
||||||
contact_us: "Kontakt os",
|
contact_us: 'Kontakt os',
|
||||||
contact_us_verification_required: "Du skal have en verificeret e-mailadresse for at bruge dette.",
|
contact_us_verification_required: 'Du skal have en verificeret e-mailadresse for at bruge dette.',
|
||||||
contain: 'Indeholde',
|
contain: 'Indeholde',
|
||||||
continue: "Fortsæt",
|
continue: 'Fortsæt',
|
||||||
copy: 'Kopier',
|
copy: 'Kopier',
|
||||||
copy_link: "Kopier link",
|
copy_link: 'Kopier link',
|
||||||
copying: "Kopierer",
|
copying: 'Kopierer',
|
||||||
copying_file: "Kopierer %%",
|
copying_file: 'Kopierer %%',
|
||||||
cover: 'Omslag',
|
cover: 'Omslag',
|
||||||
create_account: "Opret konto",
|
create_account: 'Opret konto',
|
||||||
create_free_account: "Opret gratis konto",
|
create_free_account: 'Opret gratis konto',
|
||||||
create_shortcut: "Opret genvej",
|
create_shortcut: 'Opret genvej',
|
||||||
credits: "Kreditter",
|
credits: 'Kreditter',
|
||||||
current_password: "Nuværende adgangskode",
|
current_password: 'Nuværende adgangskode',
|
||||||
cut: 'Klip',
|
cut: 'Klip',
|
||||||
clock: "Uret",
|
clock: 'Uret',
|
||||||
clock_visible_hide: 'Skjul - Altid skjult',
|
clock_visible_hide: 'Skjul - Altid skjult',
|
||||||
clock_visible_show: 'Vis - Altid synlig',
|
clock_visible_show: 'Vis - Altid synlig',
|
||||||
clock_visible_auto: 'Auto - Standard, synlig kun i fuld skærm-tilstand.',
|
clock_visible_auto: 'Auto - Standard, synlig kun i fuld skærm-tilstand.',
|
||||||
close_all: 'Luk alle',
|
close_all: 'Luk alle',
|
||||||
created: 'Oprettet',
|
created: 'Oprettet',
|
||||||
date_modified: 'Dato ændret',
|
date_modified: 'Dato ændret',
|
||||||
default: 'Standard',
|
default: 'Standard',
|
||||||
delete: 'Slet',
|
delete: 'Slet',
|
||||||
delete_account: "Slet konto",
|
delete_account: 'Slet konto',
|
||||||
delete_permanently: "Slet permanent",
|
delete_permanently: 'Slet permanent',
|
||||||
deleting_file: "Sletter %%",
|
deleting_file: 'Sletter %%',
|
||||||
deploy_as_app: 'Implementer som app',
|
deploy_as_app: 'Implementer som app',
|
||||||
descending: 'Faldende',
|
descending: 'Faldende',
|
||||||
desktop: 'Skrivebord',
|
desktop: 'Skrivebord',
|
||||||
desktop_background_fit: "Tilpas",
|
desktop_background_fit: 'Tilpas',
|
||||||
developers: "Udviklere",
|
developers: 'Udviklere',
|
||||||
dir_published_as_website: `%strong% er blevet offentliggjort til:`,
|
dir_published_as_website: `%strong% er blevet offentliggjort til:`,
|
||||||
disable_2fa: 'Deaktiver 2FA',
|
disable_2fa: 'Deaktiver 2FA',
|
||||||
disable_2fa_confirm: "Er du sikker på, at du vil deaktivere 2FA?",
|
disable_2fa_confirm: 'Er du sikker på, at du vil deaktivere 2FA?',
|
||||||
disable_2fa_instructions: "Indtast din adgangskode for at deaktivere 2FA.",
|
disable_2fa_instructions: 'Indtast din adgangskode for at deaktivere 2FA.',
|
||||||
disassociate_dir: "Fjern tilknytning til mappe",
|
disassociate_dir: 'Fjern tilknytning til mappe',
|
||||||
documents: 'Dokumenter',
|
documents: 'Dokumenter',
|
||||||
dont_allow: 'Tillad ikke',
|
dont_allow: 'Tillad ikke',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
download_file: 'Download fil',
|
download_file: 'Download fil',
|
||||||
downloading: "Downloader",
|
downloading: 'Downloader',
|
||||||
email: "E-mail",
|
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_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_invalid: 'E-mail er ugyldig.',
|
||||||
email_or_username: "E-mail eller brugernavn",
|
email_or_username: 'E-mail eller brugernavn',
|
||||||
email_required: 'E-mail er påkrævet.',
|
email_required: 'E-mail er påkrævet.',
|
||||||
empty_trash: 'Tøm papirkurv',
|
empty_trash: 'Tøm papirkurv',
|
||||||
empty_trash_confirmation: `Er du sikker på, at du vil slette elementerne i papirkurven permanent?`,
|
empty_trash_confirmation: `Er du sikker på, at du vil slette elementerne i papirkurven permanent?`,
|
||||||
emptying_trash: 'Tømmer papirkurv…',
|
emptying_trash: 'Tømmer papirkurv…',
|
||||||
enable_2fa: 'Aktivér 2FA',
|
enable_2fa: 'Aktivér 2FA',
|
||||||
end_hard: "Afslut hårdt",
|
end_hard: 'Afslut hårdt',
|
||||||
end_process_force_confirm: "Er du sikker på, at du vil afslutte denne proces tvangsmæssigt?",
|
end_process_force_confirm: 'Er du sikker på, at du vil afslutte denne proces tvangsmæssigt?',
|
||||||
end_soft: "Afslut blødt",
|
end_soft: 'Afslut blødt',
|
||||||
enlarged_qr_code: "Forstørret QR-kode",
|
enlarged_qr_code: 'Forstørret QR-kode',
|
||||||
enter_password_to_confirm_delete_user: "Indtast din adgangskode for at bekræfte sletning af konto",
|
enter_password_to_confirm_delete_user: 'Indtast din adgangskode for at bekræfte sletning af konto',
|
||||||
error_message_is_missing: "Fejlmeddelelse mangler.",
|
error_message_is_missing: 'Fejlmeddelelse mangler.',
|
||||||
error_unknown_cause: "Der opstod en ukendt fejl.",
|
error_unknown_cause: 'Der opstod en ukendt fejl.',
|
||||||
error_uploading_files: "Fejl ved upload af filer",
|
error_uploading_files: 'Fejl ved upload af filer',
|
||||||
favorites: "Favoritter",
|
favorites: 'Favoritter',
|
||||||
feedback: "Feedback",
|
feedback: 'Feedback',
|
||||||
feedback_c2a: "Brug venligst formularen nedenfor til at sende os din feedback, kommentarer og fejlrapporter.",
|
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.",
|
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",
|
fit: 'Pas',
|
||||||
folder: 'Mappe',
|
folder: 'Mappe',
|
||||||
force_quit: 'Tving afslut',
|
force_quit: 'Tving afslut',
|
||||||
forgot_pass_c2a: "Glemt adgangskode?",
|
forgot_pass_c2a: 'Glemt adgangskode?',
|
||||||
from: "Fra",
|
from: 'Fra',
|
||||||
general: "Generelt",
|
general: 'Generelt',
|
||||||
get_a_copy_of_on_puter: `Få en kopi af '%%' på Puter.com!`,
|
get_a_copy_of_on_puter: `Få en kopi af '%%' på Puter.com!`,
|
||||||
get_copy_link: 'Få kopi-link',
|
get_copy_link: 'Få kopi-link',
|
||||||
hide_all_windows: "Skjul alle vinduer",
|
hide_all_windows: 'Skjul alle vinduer',
|
||||||
home: 'Hjem',
|
home: 'Hjem',
|
||||||
html_document: 'HTML-dokument',
|
html_document: 'HTML-dokument',
|
||||||
hue: 'Farvetone',
|
hue: 'Farvetone',
|
||||||
image: 'Billede',
|
image: 'Billede',
|
||||||
incorrect_password: "Forkert adgangskode",
|
incorrect_password: 'Forkert adgangskode',
|
||||||
invite_link: "Inviter link",
|
invite_link: 'Inviter link',
|
||||||
item: 'element',
|
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.`,
|
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',
|
jpeg_image: 'JPEG-billede',
|
||||||
keep_in_taskbar: 'Hold i proceslinjen',
|
keep_in_taskbar: 'Hold i proceslinjen',
|
||||||
language: "Sprog",
|
language: 'Sprog',
|
||||||
license: "Licens",
|
license: 'Licens',
|
||||||
lightness: 'Lysstyrke',
|
lightness: 'Lysstyrke',
|
||||||
link_copied: "Link kopieret",
|
link_copied: 'Link kopieret',
|
||||||
loading: 'Indlæser',
|
loading: 'Indlæser',
|
||||||
log_in: "Log ind",
|
log_in: 'Log ind',
|
||||||
log_into_another_account_anyway: 'Log ind på en anden konto alligevel',
|
log_into_another_account_anyway: 'Log ind på en anden konto alligevel',
|
||||||
log_out: 'Log ud',
|
log_out: 'Log ud',
|
||||||
looks_good: "Ser godt ud!",
|
looks_good: 'Ser godt ud!',
|
||||||
manage_sessions: "Administrer sessioner",
|
manage_sessions: 'Administrer sessioner',
|
||||||
menubar_style: "Menubjælke stil",
|
menubar_style: 'Menubjælke stil',
|
||||||
menubar_style_desktop: "Skrivebord",
|
menubar_style_desktop: 'Skrivebord',
|
||||||
menubar_style_system: "System",
|
menubar_style_system: 'System',
|
||||||
menubar_style_window: "Vindue",
|
menubar_style_window: 'Vindue',
|
||||||
modified: 'Ændret',
|
modified: 'Ændret',
|
||||||
move: 'Flyt',
|
move: 'Flyt',
|
||||||
moving_file: "Flytter %%",
|
moving_file: 'Flytter %%',
|
||||||
my_websites: "Mine websteder",
|
my_websites: 'Mine websteder',
|
||||||
name: 'Navn',
|
name: 'Navn',
|
||||||
name_cannot_be_empty: 'Navn kan ikke være tomt.',
|
name_cannot_be_empty: 'Navn kan ikke være tomt.',
|
||||||
name_cannot_contain_double_period: "Navn kan ikke være '..' tegn.",
|
name_cannot_contain_double_period: "Navn kan ikke være '..' tegn.",
|
||||||
name_cannot_contain_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_cannot_contain_slash: "Navn kan ikke indeholde '/' tegn.",
|
||||||
name_must_be_string: "Navn kan kun være en streng.",
|
name_must_be_string: 'Navn kan kun være en streng.',
|
||||||
name_too_long: `Navn kan ikke være længere end %% tegn.`,
|
name_too_long: `Navn kan ikke være længere end %% tegn.`,
|
||||||
new: 'Ny',
|
new: 'Ny',
|
||||||
new_email: 'Ny e-mail',
|
new_email: 'Ny e-mail',
|
||||||
new_folder: 'Ny mappe',
|
new_folder: 'Ny mappe',
|
||||||
new_password: "Ny adgangskode",
|
new_password: 'Ny adgangskode',
|
||||||
new_username: "Nyt brugernavn",
|
new_username: 'Nyt brugernavn',
|
||||||
no: 'Nej',
|
no: 'Nej',
|
||||||
no_dir_associated_with_site: 'Ingen mappe tilknyttet denne adresse.',
|
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.",
|
no_websites_published: 'Du har ikke offentliggjort nogen websteder endnu. Højreklik på en mappe for at komme i gang.',
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
open: "Åbn",
|
open: 'Åbn',
|
||||||
open_in_new_tab: "Åbn i ny fane",
|
open_in_new_tab: 'Åbn i ny fane',
|
||||||
open_in_new_window: "Åbn i nyt vindue",
|
open_in_new_window: 'Åbn i nyt vindue',
|
||||||
open_with: "Åbn med",
|
open_with: 'Åbn med',
|
||||||
original_name: 'Originalt navn',
|
original_name: 'Originalt navn',
|
||||||
original_path: 'Original sti',
|
original_path: 'Original sti',
|
||||||
oss_code_and_content: "Open Source Software og indhold",
|
oss_code_and_content: 'Open Source Software og indhold',
|
||||||
password: "Adgangskode",
|
password: 'Adgangskode',
|
||||||
password_changed: "Adgangskode ændret.",
|
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_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_token_invalid: 'Denne adgangskodegendannelsestoken er ikke længere gyldig.',
|
||||||
password_recovery_unknown_error: "Der opstod en ukendt fejl. Prøv venligst igen senere.",
|
password_recovery_unknown_error: 'Der opstod en ukendt fejl. Prøv venligst igen senere.',
|
||||||
password_required: 'Adgangskode er påkrævet.',
|
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.",
|
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.',
|
passwords_do_not_match: '`Ny adgangskode` og `Bekræft ny adgangskode` stemmer ikke overens.',
|
||||||
paste: 'Sæt ind',
|
paste: 'Sæt ind',
|
||||||
paste_into_folder: "Sæt ind i mappe",
|
paste_into_folder: 'Sæt ind i mappe',
|
||||||
path: 'Sti',
|
path: 'Sti',
|
||||||
personalization: "Personalisering",
|
personalization: 'Personalisering',
|
||||||
pick_name_for_website: "Vælg et navn til dit websted:",
|
pick_name_for_website: 'Vælg et navn til dit websted:',
|
||||||
picture: "Billede",
|
picture: 'Billede',
|
||||||
pictures: 'Billeder',
|
pictures: 'Billeder',
|
||||||
plural_suffix: 's',
|
plural_suffix: 's',
|
||||||
powered_by_puter_js: `Udviklet af {{link=docs}}Puter.js{{/link}}`,
|
powered_by_puter_js: `Udviklet af {{link=docs}}Puter.js{{/link}}`,
|
||||||
preparing: "Forbereder...",
|
preparing: 'Forbereder...',
|
||||||
preparing_for_upload: "Forbereder til upload...",
|
preparing_for_upload: 'Forbereder til upload...',
|
||||||
print: 'Udskriv',
|
print: 'Udskriv',
|
||||||
privacy: "Privatliv",
|
privacy: 'Privatliv',
|
||||||
proceed_to_login: 'Fortsæt til login',
|
proceed_to_login: 'Fortsæt til login',
|
||||||
proceed_with_account_deletion: "Fortsæt med sletning af konto",
|
proceed_with_account_deletion: 'Fortsæt med sletning af konto',
|
||||||
process_status_initializing: "Initialiserer",
|
process_status_initializing: 'Initialiserer',
|
||||||
process_status_running: "Kører",
|
process_status_running: 'Kører',
|
||||||
process_type_app: 'App',
|
process_type_app: 'App',
|
||||||
process_type_init: 'Init',
|
process_type_init: 'Init',
|
||||||
process_type_ui: 'UI',
|
process_type_ui: 'UI',
|
||||||
properties: "Egenskaber",
|
properties: 'Egenskaber',
|
||||||
public: 'Offentlig',
|
public: 'Offentlig',
|
||||||
publish: "Offentliggør",
|
publish: 'Offentliggør',
|
||||||
publish_as_website: 'Offentliggør som websted',
|
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.`,
|
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%",
|
reading_file: 'Læser %strong%',
|
||||||
recent: "Seneste",
|
recent: 'Seneste',
|
||||||
recommended: "Anbefalet",
|
recommended: 'Anbefalet',
|
||||||
recover_password: "Gendan adgangskode",
|
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_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!`,
|
refer_friends_social_media_c2a: `Få 1 GB gratis lagerplads på Puter.com!`,
|
||||||
refresh: 'Opdater',
|
refresh: 'Opdater',
|
||||||
release_address_confirmation: `Er du sikker på, at du vil frigive denne adresse?`,
|
release_address_confirmation: `Er du sikker på, at du vil frigive denne adresse?`,
|
||||||
remove_from_taskbar: 'Fjern fra proceslinje',
|
remove_from_taskbar: 'Fjern fra proceslinje',
|
||||||
rename: 'Omdøb',
|
rename: 'Omdøb',
|
||||||
repeat: 'Gentag',
|
repeat: 'Gentag',
|
||||||
replace: 'Erstat',
|
replace: 'Erstat',
|
||||||
replace_all: 'Erstat alle',
|
replace_all: 'Erstat alle',
|
||||||
resend_confirmation_code: "Send bekræftelseskode igen",
|
resend_confirmation_code: 'Send bekræftelseskode igen',
|
||||||
reset_colors: "Nulstil farver",
|
reset_colors: 'Nulstil farver',
|
||||||
restart_puter_confirm: "Er du sikker på, at du vil genstarte Puter?",
|
restart_puter_confirm: 'Er du sikker på, at du vil genstarte Puter?',
|
||||||
restore: "Gendan",
|
restore: 'Gendan',
|
||||||
save: 'Gem',
|
save: 'Gem',
|
||||||
saturation: 'Mætning',
|
saturation: 'Mætning',
|
||||||
save_account: 'Gem konto',
|
save_account: 'Gem konto',
|
||||||
save_account_to_get_copy_link: "Opret venligst en konto for at fortsætte.",
|
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_account_to_publish: 'Opret venligst en konto for at fortsætte.',
|
||||||
save_session: 'Gem session',
|
save_session: 'Gem session',
|
||||||
save_session_c2a: 'Opret en konto for at gemme din nuværende session og undgå at miste dit arbejde.',
|
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_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_2fa: 'Scan QR-koden med din autentifikator-app',
|
||||||
scan_qr_generic: 'Scan denne QR-kode med din telefon eller en anden enhed',
|
scan_qr_generic: 'Scan denne QR-kode med din telefon eller en anden enhed',
|
||||||
search: 'Søg',
|
search: 'Søg',
|
||||||
seconds: 'sekunder',
|
seconds: 'sekunder',
|
||||||
security: "Sikkerhed",
|
security: 'Sikkerhed',
|
||||||
select: "Vælg",
|
select: 'Vælg',
|
||||||
selected: 'valgt',
|
selected: 'valgt',
|
||||||
select_color: 'Vælg farve…',
|
select_color: 'Vælg farve…',
|
||||||
sessions: "Sessioner",
|
sessions: 'Sessioner',
|
||||||
send: "Send",
|
send: 'Send',
|
||||||
send_password_recovery_email: "Send e-mail til gendannelse af adgangskode",
|
send_password_recovery_email: 'Send e-mail til gendannelse af adgangskode',
|
||||||
session_saved: "Tak fordi du oprettede en konto. Denne session er blevet gemt.",
|
session_saved: 'Tak fordi du oprettede en konto. Denne session er blevet gemt.',
|
||||||
settings: "Indstillinger",
|
settings: 'Indstillinger',
|
||||||
set_new_password: "Indstil ny adgangskode",
|
set_new_password: 'Indstil ny adgangskode',
|
||||||
share: "Del",
|
share: 'Del',
|
||||||
share_to: "Del til",
|
share_to: 'Del til',
|
||||||
share_with: "Del med:",
|
share_with: 'Del med:',
|
||||||
shortcut_to: "Genvej til",
|
shortcut_to: 'Genvej til',
|
||||||
show_all_windows: "Vis alle vinduer",
|
show_all_windows: 'Vis alle vinduer',
|
||||||
show_hidden: 'Vis skjulte',
|
show_hidden: 'Vis skjulte',
|
||||||
sign_in_with_puter: "Log ind med Puter",
|
sign_in_with_puter: 'Log ind med Puter',
|
||||||
sign_up: "Tilmeld dig",
|
sign_up: 'Tilmeld dig',
|
||||||
signing_in: "Logger ind…",
|
signing_in: 'Logger ind…',
|
||||||
size: 'Størrelse',
|
size: 'Størrelse',
|
||||||
skip: 'Spring over',
|
skip: 'Spring over',
|
||||||
something_went_wrong: "Noget gik galt.",
|
something_went_wrong: 'Noget gik galt.',
|
||||||
sort_by: 'Sorter efter',
|
sort_by: 'Sorter efter',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
status: "Status",
|
status: 'Status',
|
||||||
storage_usage: "Lagerforbrug",
|
storage_usage: 'Lagerforbrug',
|
||||||
storage_puter_used: 'brugt af Puter',
|
storage_puter_used: 'brugt af Puter',
|
||||||
taking_longer_than_usual: 'Tager lidt længere tid end normalt. Vent venligst...',
|
taking_longer_than_usual: 'Tager lidt længere tid end normalt. Vent venligst...',
|
||||||
task_manager: "Opgavehåndtering",
|
task_manager: 'Opgavehåndtering',
|
||||||
taskmgr_header_name: "Navn",
|
taskmgr_header_name: 'Navn',
|
||||||
taskmgr_header_status: "Status",
|
taskmgr_header_status: 'Status',
|
||||||
taskmgr_header_type: "Type",
|
taskmgr_header_type: 'Type',
|
||||||
terms: "Vilkår",
|
terms: 'Vilkår',
|
||||||
text_document: 'Tekstdokument',
|
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}}.`,
|
tos_fineprint: `Ved at klikke på 'Opret gratis konto' accepterer du Puters {{link=terms}}Brugsvilkår{{/link}} og {{link=privacy}}Privatlivspolitik{{/link}}.`,
|
||||||
transparency: "Gennemsigtighed",
|
transparency: 'Gennemsigtighed',
|
||||||
trash: 'Papirkurv',
|
trash: 'Papirkurv',
|
||||||
two_factor: 'To-faktor autentifikation',
|
two_factor: 'To-faktor autentifikation',
|
||||||
two_factor_disabled: '2FA deaktiveret',
|
two_factor_disabled: '2FA deaktiveret',
|
||||||
two_factor_enabled: '2FA aktiveret',
|
two_factor_enabled: '2FA aktiveret',
|
||||||
type: 'Type',
|
type: 'Type',
|
||||||
type_confirm_to_delete_account: "Skriv 'bekræft' for at slette din konto.",
|
type_confirm_to_delete_account: "Skriv 'bekræft' for at slette din konto.",
|
||||||
ui_colors: "UI-farver",
|
ui_colors: 'UI-farver',
|
||||||
ui_manage_sessions: "Session Manager",
|
ui_manage_sessions: 'Session Manager',
|
||||||
ui_revoke: "Tilbagekald",
|
ui_revoke: 'Tilbagekald',
|
||||||
undo: 'Fortryd',
|
undo: 'Fortryd',
|
||||||
unlimited: 'Ubegrænset',
|
unlimited: 'Ubegrænset',
|
||||||
unzip: "Udpak",
|
unzip: 'Udpak',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
upload_here: 'Upload her',
|
upload_here: 'Upload her',
|
||||||
usage: 'Brug',
|
usage: 'Brug',
|
||||||
username: "Brugernavn",
|
username: 'Brugernavn',
|
||||||
username_changed: 'Brugernavn opdateret succesfuldt.',
|
username_changed: 'Brugernavn opdateret succesfuldt.',
|
||||||
username_required: 'Brugernavn er påkrævet.',
|
username_required: 'Brugernavn er påkrævet.',
|
||||||
versions: "Versioner",
|
versions: 'Versioner',
|
||||||
videos: 'Videoer',
|
videos: 'Videoer',
|
||||||
visibility: 'Synlighed',
|
visibility: 'Synlighed',
|
||||||
yes: 'Ja',
|
yes: 'Ja',
|
||||||
yes_release_it: 'Ja, frigiv det',
|
yes_release_it: 'Ja, frigiv det',
|
||||||
you_have_been_referred_to_puter_by_a_friend: "Du er blevet henvist til Puter af en ven!",
|
you_have_been_referred_to_puter_by_a_friend: 'Du er blevet henvist til Puter af en ven!',
|
||||||
zip: "Zip",
|
zip: 'Zip',
|
||||||
zipping_file: "Zipper %strong%",
|
zipping_file: 'Zipper %strong%',
|
||||||
|
|
||||||
// === 2FA Setup ===
|
// === 2FA Setup ===
|
||||||
setup2fa_1_step_heading: 'Åbn din autentifikator-app',
|
setup2fa_1_step_heading: 'Åbn din autentifikator-app',
|
||||||
setup2fa_1_instructions: `
|
setup2fa_1_instructions: `
|
||||||
Du kan bruge enhver autentifikator-app, der understøtter Time-based One-Time Password (TOTP) protokollen.
|
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
|
Der er mange at vælge imellem, men hvis du er usikker
|
||||||
<a target="_blank" href="https://authy.com/download">Authy</a>
|
<a target="_blank" href="https://authy.com/download">Authy</a>
|
||||||
er et solidt valg til Android og iOS.
|
er et solidt valg til Android og iOS.
|
||||||
`,
|
`,
|
||||||
setup2fa_2_step_heading: 'Scan QR-koden',
|
setup2fa_2_step_heading: 'Scan QR-koden',
|
||||||
setup2fa_3_step_heading: 'Indtast den 6-cifrede kode',
|
setup2fa_3_step_heading: 'Indtast den 6-cifrede kode',
|
||||||
setup2fa_4_step_heading: 'Kopier dine gendannelseskoder',
|
setup2fa_4_step_heading: 'Kopier dine gendannelseskoder',
|
||||||
setup2fa_4_instructions: `
|
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.
|
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.
|
Sørg for at opbevare dem på et sikkert sted.
|
||||||
`,
|
`,
|
||||||
setup2fa_5_step_heading: 'Bekræft 2FA-opsætning',
|
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_1: 'Jeg har gemt mine gendannelseskoder på et sikkert sted',
|
||||||
setup2fa_5_confirmation_2: 'Jeg er klar til at aktivere 2FA',
|
setup2fa_5_confirmation_2: 'Jeg er klar til at aktivere 2FA',
|
||||||
setup2fa_5_button: 'Aktivér 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',
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// === 2FA Login ===
|
||||||
// Missing translations:
|
login2fa_otp_title: 'Indtast 2FA-kode',
|
||||||
// ----------------------------------------
|
login2fa_otp_instructions: 'Indtast den 6-cifrede kode fra din autentifikator-app.',
|
||||||
"change": 'Ændre', // In English: "Change"
|
login2fa_recovery_title: 'Indtast en gendannelseskode',
|
||||||
"clock_visibility": 'Udsyn af ur', // In English: "Clock Visibility"
|
login2fa_recovery_instructions: 'Indtast en af dine gendannelseskoder for at få adgang til din konto.',
|
||||||
"reading": 'Læsening %strong%', // In English: "Reading %strong%"
|
login2fa_use_recovery_code: 'Brug en gendannelseskode',
|
||||||
"writing": 'Skrivning %strong%', // In English: "Writing %strong%"
|
login2fa_recovery_back: 'Tilbage',
|
||||||
"unzipping": 'Udpakker %strong%', // In English: "Unzipping %strong%"
|
login2fa_recovery_placeholder: 'XXXXXXXX',
|
||||||
"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.
|
change: 'Ændre', // In English: "Change"
|
||||||
"Editor": 'Redaktør', // In English: "Editor"
|
clock_visibility: 'Vis ur', // In English: "Clock Visibility"
|
||||||
"Viewer": 'Seer', // In English: "Viewer"
|
reading: 'Læse %strong%', // In English: "Reading %strong%"
|
||||||
"People with access": 'Persom med adgang', // In English: "People with access"
|
writing: 'Skrive %strong%', // In English: "Writing %strong%"
|
||||||
"Share With…": 'Del med…', // In English: "Share With…"
|
unzipping: 'Udpakke %strong%', // In English: "Unzipping %strong%"
|
||||||
"Owner": 'Ejer', // In English: "Owner"
|
sequencing: 'Sekvensering %strong%', // In English: "Sequencing %strong%"
|
||||||
"You can't share with yourself.": 'Du kan ikke dele med dig selv.', // In English: "You can't share with yourself."
|
zipping: 'Komprimer %strong%', // In English: "Zipping %strong%"
|
||||||
"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"
|
Editor: 'Redaktør', // In English: "Editor"
|
||||||
"billing.change_payment_method": 'Skift betalingsmetode', // In English: "Change"
|
Viewer: 'Seer', // In English: "Viewer"
|
||||||
"billing.cancel":'Annuller', // In English: "Cancel"
|
'People with access': 'Personer med adgang', // In English: "People with access"
|
||||||
"billing.download_invoice": 'Download faktura', // In English: "Download"
|
'Share With…': 'Del med...', // In English: "Share With…"
|
||||||
"billing.payment_method": 'Betalingsmetode', // In English: "Payment Method"
|
Owner: 'Ejer', // In English: "Owner"
|
||||||
"billing.payment_method_updated":'Betalingsmetode opdateret!', // In English: "Payment method updated!"
|
"You can't share with yourself.": 'Du kan ikke dele med dig selv.', // In English: "You can't share with yourself."
|
||||||
"billing.confirm_payment_method": 'Bekræft betalingsmetode', // In English: "Confirm Payment Method"
|
'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.payment_history": 'Betalingshistorik', // In English: "Payment History"
|
'billing.change_payment_method': 'Ændre', // In English: "Change"
|
||||||
"billing.refunded": 'Refunderet', // In English: "Refunded"
|
'billing.cancel': 'Annuller', // In English: "Cancel"
|
||||||
"billing.paid": 'Betalt', // In English: "Paid"
|
'billing.download_invoice': 'Download', // In English: "Download"
|
||||||
"billing.ok": 'OK', // In English: "OK"
|
'billing.payment_method': 'Betalingsmetode', // In English: "Payment Method"
|
||||||
"billing.resume_subscription": 'Genoptag abonnement', // In English: "Resume Subscription"
|
'billing.payment_method_updated': 'Betalingsmetode opdateret!', // In English: "Payment method updated!"
|
||||||
"billing.subscription_cancelled":'Dit abonnement er blevet annulleret.', // In English: "Your subscription has been canceled."
|
'billing.confirm_payment_method': 'Bekræft betalingsmetode', // In English: "Confirm Payment Method"
|
||||||
"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.payment_history': 'Betalings historik', // In English: "Payment History"
|
||||||
"billing.offering.free": 'Gratis', // In English: "Free"
|
'billing.refunded': 'Refunderet', // In English: "Refunded"
|
||||||
"billing.offering.pro":'Professionel', // In English: "Professional"
|
'billing.paid': 'Betalt', // In English: "Paid"
|
||||||
"billing.offering.business": 'Erhverv', // In English: "Business"
|
'billing.ok': 'OK', // In English: "OK"
|
||||||
"billing.cloud_storage": 'Cloud-lagring', // In English: "Cloud Storage"
|
'billing.resume_subscription': 'Genoptag abonnement', // In English: "Resume Subscription"
|
||||||
"billing.ai_access": 'AI Adgang', // In English: "AI Access"
|
'billing.subscription_cancelled': 'Dit abonnement er blevet annulleret.', // In English: "Your subscription has been canceled."
|
||||||
"billing.bandwidth": 'Båndbredde', // In English: "Bandwidth"
|
'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.apps_and_games": 'Apps & Spil', // In English: "Apps & Games"
|
'billing.offering.free': 'Gratis', // In English: "Free"
|
||||||
"billing.upgrade_to_pro": 'Opgrader til %strong%', // In English: "Upgrade to %strong%"
|
'billing.offering.pro': 'Professionel', // In English: "Professional"
|
||||||
"billing.switch_to": 'Skift til %strong%', // In English: "Switch to %strong%"
|
'billing.offering.business': 'Virksomhed', // In English: "Business"
|
||||||
"billing.payment_setup": 'Betalingsopsætning', // In English: "Payment Setup"
|
'billing.cloud_storage': 'Opbevaring i skyen', // In English: "Cloud Storage"
|
||||||
"billing.back": 'Tilbage', // In English: "Back"
|
'billing.ai_access': 'AI Adgang', // In English: "AI Access"
|
||||||
"billing.you_are_now_subscribed_to": 'Du er nu abonneret på %strong% plan.', // In English: "You are now subscribed to %strong% tier.", //
|
'billing.bandwidth': 'Båndbredde', // In English: "Bandwidth"
|
||||||
"billing.you_are_now_subscribed_to_without_tier": 'Du er nu abonneret', // In English: "You are now subscribed"
|
'billing.apps_and_games': 'Apps & Spil', // In English: "Apps & Games"
|
||||||
"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.upgrade_to_pro': 'Opgrader til %strong%', // In English: "Upgrade to %strong%"
|
||||||
"billing.subscription_setup": 'Abonnementsopsætning', // In English: "Subscription Setup"
|
'billing.switch_to': 'Skift til %strong%', // In English: "Switch to %strong%"
|
||||||
"billing.cancel_it": 'Annuller det', // In English: "Cancel It"
|
'billing.payment_setup': 'Betalingsopsætning', // In English: "Payment Setup"
|
||||||
"billing.keep_it": 'Behold det', // In English: "Keep It"
|
'billing.back': 'Tilbage', // In English: "Back"
|
||||||
"billing.subscription_resumed": 'Dit %strong% abonnement er blevet genoptaget!', // In English: "Your %strong% subscription has been resumed!"
|
'billing.you_are_now_subscribed_to': 'Du abonnerer nu på niveauet %strong%.', // In English: "You are now subscribed to %strong% tier."
|
||||||
"billing.upgrade_now": 'Opgrader nu', // In English: "Upgrade Now"
|
'billing.you_are_now_subscribed_to_without_tier': 'Du er nu tilmeldt', // In English: "You are now subscribed"
|
||||||
"billing.upgrade": 'Opgrader', // In English: "Upgrade"
|
'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.currently_on_free_plan": 'Du er i øjeblikket på gratisplanen.', // In English: "You are currently on the free plan."
|
'billing.subscription_setup': 'Abonnementsopsætning', // In English: "Subscription Setup"
|
||||||
"billing.download_receipt": 'Download kvittering', // In English: "Download Receipt"
|
'billing.cancel_it': 'Annuler det', // In English: "Cancel It"
|
||||||
"billing.subscription_check_error": 'Der opstod et problem, mens vi kontrollerede din abonnementsstatus.', // In English: "A problem occurred while checking your subscription status."
|
'billing.keep_it': 'Behold det', // In English: "Keep It"
|
||||||
"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.subscription_resumed': 'Dit %strong% abonnement er genoptaget!', // In English: "Your %strong% subscription has been resumed!"
|
||||||
"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.upgrade_now': 'Opgrader nu', // In English: "Upgrade Now"
|
||||||
"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.upgrade': 'Opgrader', // In English: "Upgrade"
|
||||||
"billing.current_plan": 'Nuværende plan', // In English: "Current plan"
|
'billing.currently_on_free_plan': 'Du er i øjeblikket på det gratis plan.', // In English: "You are currently on the free plan."
|
||||||
"billing.cancelled_subscription_tier": 'Annulleret abonnement (%%)', // In English: "Cancelled Subscription (%%)"
|
'billing.download_receipt': 'Download kvittering', // In English: "Download Receipt"
|
||||||
"billing.manage": 'Administrer', // In English: "Manage"
|
'billing.subscription_check_error': 'Der opstod et problem under kontrol af din abonnementsstatus', // In English: "A problem occurred while checking your subscription status."
|
||||||
"billing.limited": 'Begrænset', // In English: "Limited"
|
'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.expanded": 'Udvidet', // In English: "Expanded"
|
'billing.sub_cancelled_but_valid_until':
|
||||||
"billing.accelerated": 'Accelereret', // In English: "Accelerated"
|
'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.enjoy_msg": 'Nyd %% af cloud-lagring plus andre fordele.', // In English: "Enjoy %% of Cloud Storage plus other benefits."
|
'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;
|
export default da;
|
||||||
|
Loading…
Reference in New Issue
Block a user