diff --git a/.gitignore b/.gitignore
index 7855d3b36e..ab697c25d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,7 @@ dist/
# this is for jetbrain IDEs
.idea/
/puter
+
+# Local Netlify folder
+.netlify
+src/emulator/release/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 40a50189cf..7b01a93997 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -45,15 +45,18 @@ If you'd like to contribute code to Puter, you need to fork the project and subm
We'll review your pull request and work with you to get your changes merged into the project.
+## Repository Structure
+
+![file structure](./doc/File%20Structure.drawio.png)
+
## Your first code contribution
We maintain a list of issues that are good for first-time contributors. You can find these issues by searching for the [`good first issue`](https://github.com/HeyPuter/puter/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label in our [GitHub repository](https://github.com/HeyPuter/puter). These issues are designed to be relatively easy to fix, and we're happy to help you get started. Pick an issue that interests you, and leave a comment on the issue to let us know you're working on it.
-
-
## Documentation for Contributors
-See [doc/contributors/index.md](./doc/contributors/index.md) for more information.
+### Backend
+See [src/backend/CONTRIBUTING.md](src/backend/CONTRIBUTING.md)
diff --git a/awesome/#DoesItRunPuter.md b/awesome/#DoesItRunPuter.md
index bb737e5717..6c6cc7e9e7 100644
--- a/awesome/#DoesItRunPuter.md
+++ b/awesome/#DoesItRunPuter.md
@@ -13,3 +13,4 @@
- [Steam Deck](https://twitter.com/everythingSung/status/1782162352403828793)
- [Ladybird Browser](https://x.com/HeyPuter/status/1810783504503800035)
- [Garry's Mod](https://x.com/HeyPuter/status/1850587712786722862)
+- [Samsung Q88BA](https://x.com/AmirIsAround/status/1862614583263076540)
diff --git a/doc/File Structure.drawio b/doc/File Structure.drawio
new file mode 100644
index 0000000000..2af3e4a1c6
--- /dev/null
+++ b/doc/File Structure.drawio
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/File Structure.drawio.png b/doc/File Structure.drawio.png
new file mode 100644
index 0000000000..16de66fa4f
Binary files /dev/null and b/doc/File Structure.drawio.png differ
diff --git a/doc/contributors/extensions.md b/doc/contributors/extensions.md
index 12cd44d636..e95570e644 100644
--- a/doc/contributors/extensions.md
+++ b/doc/contributors/extensions.md
@@ -1,2 +1,3 @@
-### `vscode`
-- `es6-string-html`
+## Puter Extensions
+
+See the [Wiki Page](https://github.com/HeyPuter/puter/wiki/ex_extensions)
diff --git a/doc/contributors/vscode.md b/doc/contributors/vscode.md
new file mode 100644
index 0000000000..12cd44d636
--- /dev/null
+++ b/doc/contributors/vscode.md
@@ -0,0 +1,2 @@
+### `vscode`
+- `es6-string-html`
diff --git a/doc/i18n/README.zh.md b/doc/i18n/README.zh.md
index bf04949934..7022380f92 100644
--- a/doc/i18n/README.zh.md
+++ b/doc/i18n/README.zh.md
@@ -89,6 +89,15 @@ docker compose up
```
+## 宝塔面板Docker一键部署(推荐)
+
+1. 安装宝塔面板9.2.0及以上版本,前往 [宝塔面板](https://www.bt.cn/new/download.html?r=dk_puter) 官网,选择正式版的脚本下载安装
+
+2. 安装后登录宝塔面板,在左侧菜单栏中点击 `Docker`,首次进入会提示安装`Docker`服务,点击立即安装,按提示完成安装
+
+3. 安装完成后在应用商店中搜索`puter`,点击安装,配置域名等基本信息即可完成安装
+
+
### ☁️ Puter.com
Puter 可以作为托管服务使用,访问 [**puter.com**](https://puter.com)。
diff --git a/experiments/x86emu/www/main.js b/experiments/x86emu/www/main.js
index 69df0af813..b936876ed6 100755
--- a/experiments/x86emu/www/main.js
+++ b/experiments/x86emu/www/main.js
@@ -17,7 +17,6 @@
* along with this program. If not, see .
*/
-#!/usr/bin/env node
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
diff --git a/mods/mods_available/kdmod/ShareTestService.js b/mods/mods_available/kdmod/ShareTestService.js
index 3e8e61af92..94bf87bea9 100644
--- a/mods/mods_available/kdmod/ShareTestService.js
+++ b/mods/mods_available/kdmod/ShareTestService.js
@@ -21,7 +21,6 @@
// we have these things registered in "useapi".
const {
get_user,
- generate_system_fsentries,
invalidate_cached_user,
deleteUser,
} = require('../../../src/backend/src/helpers.js');
@@ -146,7 +145,8 @@ class ShareTestService extends use.Service {
],
);
const user = await get_user({ username });
- await generate_system_fsentries(user);
+ const svc_user = this.services.get('user');
+ await svc_user.generate_default_fsentries({ user });
invalidate_cached_user(user);
return user;
}
diff --git a/package-lock.json b/package-lock.json
index 93984e84a2..7be249a69c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"javascript-time-ago": "^2.5.11",
"json-colorizer": "^3.0.1",
"open": "^10.1.0",
+ "sharp": "^0.33.5",
"simple-git": "^3.25.0",
"string-template": "^1.0.0",
"uuid": "^9.0.1"
@@ -1897,13 +1898,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
- "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
- "dev": true,
- "license": "MIT",
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dependencies": {
- "@babel/highlight": "^7.24.7",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
@@ -1969,15 +1969,15 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
- "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
- "license": "MIT",
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
+ "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
"dependencies": {
- "@babel/types": "^7.24.7",
+ "@babel/parser": "^7.26.2",
+ "@babel/types": "^7.26.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^2.5.1"
+ "jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
@@ -2023,33 +2023,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-function-name": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
- "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.24.7",
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-hoist-variables": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
- "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-module-imports": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
@@ -2112,19 +2085,17 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
- "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
- "license": "MIT",
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
- "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
- "license": "MIT",
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"engines": {
"node": ">=6.9.0"
}
@@ -2153,82 +2124,13 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/highlight": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
- "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.24.7",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
- "license": "MIT",
+ "node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
"dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
+ "@babel/types": "^7.26.0"
},
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/@babel/highlight/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
- "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
- "license": "MIT",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -2250,35 +2152,28 @@
}
},
"node_modules/@babel/template": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
- "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
- "dev": true,
- "license": "MIT",
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
+ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
"dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/parser": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "@babel/code-frame": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/types": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
- "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.24.7",
- "@babel/helper-environment-visitor": "^7.24.7",
- "@babel/helper-function-name": "^7.24.7",
- "@babel/helper-hoist-variables": "^7.24.7",
- "@babel/helper-split-export-declaration": "^7.24.7",
- "@babel/parser": "^7.24.7",
- "@babel/types": "^7.24.7",
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz",
+ "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/generator": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.25.9",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -2290,21 +2185,18 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/types": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
- "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
- "license": "MIT",
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
+ "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
"dependencies": {
- "@babel/helper-string-parser": "^7.24.7",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
},
"engines": {
"node": ">=6.9.0"
@@ -2339,6 +2231,15 @@
"node": ">=10.0.0"
}
},
+ "node_modules/@emnapi/runtime": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
+ "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -2596,8 +2497,8 @@
"form-data": "^4.0.0"
}
},
- "node_modules/@heyputer/parsely": {
- "resolved": "src/parsely",
+ "node_modules/@heyputer/parsers": {
+ "resolved": "src/parsers",
"link": true
},
"node_modules/@heyputer/phoenix": {
@@ -2666,6 +2567,348 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -7800,9 +8043,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+ "version": "1.7.8",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
+ "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@@ -8634,6 +8877,10 @@
"resolved": "tools/comment-parser",
"link": true
},
+ "node_modules/comment-writer": {
+ "resolved": "tools/comment-writer",
+ "link": true
+ },
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -8843,10 +9090,6 @@
"node": ">= 0.6"
}
},
- "node_modules/contextlink": {
- "resolved": "src/contextlink",
- "link": true
- },
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
@@ -9215,7 +9458,6 @@
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
"integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
- "license": "MIT",
"peerDependencies": {
"babel-plugin-macros": "^3.1.0"
},
@@ -9422,6 +9664,17 @@
"node": ">=8"
}
},
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/dom-converter": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@@ -9624,7 +9877,6 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
- "license": "MIT",
"dependencies": {
"ansi-colors": "^4.1.1",
"strip-ansi": "^6.0.1"
@@ -9883,7 +10135,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
@@ -12045,9 +12296,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@@ -12062,15 +12311,14 @@
}
},
"node_modules/jsesc": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
- "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
- "license": "MIT",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
- "node": ">=4"
+ "node": ">=6"
}
},
"node_modules/json-bigint": {
@@ -13120,6 +13368,10 @@
"integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==",
"license": "MIT"
},
+ "node_modules/module-docgen": {
+ "resolved": "tools/module-docgen",
+ "link": true
+ },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -15089,12 +15341,9 @@
"license": "MIT"
},
"node_modules/semver": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"bin": {
"semver": "bin/semver.js"
},
@@ -15102,22 +15351,6 @@
"node": ">=10"
}
},
- "node_modules/semver/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/semver/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
- },
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
@@ -15242,6 +15475,56 @@
"node": ">=8"
}
},
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/sharp/node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -15612,10 +15895,6 @@
"node": ">= 0.8"
}
},
- "node_modules/strataparse": {
- "resolved": "src/strataparse",
- "link": true
- },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -16052,15 +16331,6 @@
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
"license": "MIT"
},
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -16842,6 +17112,14 @@
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
"license": "MIT"
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@@ -17412,6 +17690,7 @@
},
"src/contextlink": {
"version": "0.0.0",
+ "extraneous": true,
"license": "AGPL-3.0-only",
"devDependencies": {
"mocha": "^10.2.0"
@@ -17489,6 +17768,12 @@
"src/parsely": {
"name": "@heyputer/parsely",
"version": "1.0.0",
+ "extraneous": true,
+ "license": "AGPL-3.0-only"
+ },
+ "src/parsers": {
+ "name": "@heyputer/parsers",
+ "version": "1.0.0",
"license": "AGPL-3.0-only"
},
"src/phoenix": {
@@ -17594,6 +17879,7 @@
},
"src/strataparse": {
"version": "0.0.0",
+ "extraneous": true,
"license": "AGPL-3.0-only"
},
"src/terminal": {
@@ -17693,6 +17979,20 @@
"node": ">= 14.16"
}
},
+ "tools/comment-writer": {
+ "version": "1.0.0",
+ "license": "AGPL-3.0-only",
+ "dependencies": {
+ "axios": "^1.7.8",
+ "console-table-printer": "^2.12.1",
+ "dedent": "^1.5.3",
+ "diff-match-patch": "^1.0.5",
+ "enquirer": "^2.4.1",
+ "js-levenshtein": "^1.1.6",
+ "word-wrap": "^1.2.5",
+ "yaml": "^2.4.5"
+ }
+ },
"tools/file-walker": {
"version": "1.0.0",
"license": "AGPL-3.0-only"
@@ -17738,6 +18038,16 @@
"node": ">=18"
}
},
+ "tools/module-docgen": {
+ "version": "1.0.0",
+ "license": "AGPL-3.0-only",
+ "dependencies": {
+ "@babel/parser": "^7.26.2",
+ "@babel/traverse": "^7.25.9",
+ "dedent": "^1.5.3",
+ "doctrine": "^3.0.0"
+ }
+ },
"tools/token-count-accuracy": {
"version": "1.0.0",
"license": "AGPL-3.0-only"
diff --git a/package.json b/package.json
index 613ccb2e9e..520f32b462 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"webpack-cli": "^5.1.1"
},
"scripts": {
- "test": "npx mocha src/phoenix/test src/contextlink/test && node src/backend/tools/test",
+ "test": "npx mocha src/phoenix/test && node src/backend/tools/test",
"start=gui": "nodemon --exec \"node dev-server.js\" ",
"start": "node ./tools/run-selfhosted.js",
"build": "cd src/gui; node ./build.js",
@@ -50,6 +50,7 @@
"javascript-time-ago": "^2.5.11",
"json-colorizer": "^3.0.1",
"open": "^10.1.0",
+ "sharp": "^0.33.5",
"simple-git": "^3.25.0",
"string-template": "^1.0.0",
"uuid": "^9.0.1"
diff --git a/src/backend/doc/contributors/index.md b/src/backend/CONTRIBUTING.md
similarity index 74%
rename from src/backend/doc/contributors/index.md
rename to src/backend/CONTRIBUTING.md
index 381aeef0df..a3101335da 100644
--- a/src/backend/doc/contributors/index.md
+++ b/src/backend/CONTRIBUTING.md
@@ -1,18 +1,22 @@
# Contributing to Puter's Backend
+## File Structure
+
+
+
## Architecture
-- [boot sequence](./boot-sequence.md)
-- [modules and services](./modules.md)
+- [boot sequence](./doc/contributors/boot-sequence.md)
+- [modules and services](./doc/contributors/modules.md)
## Features
-- [protected apps](../features/protected-apps.md)
-- [service scripts](../features/service-scripts.md)
+- [protected apps](./doc/features/protected-apps.md)
+- [service scripts](./doc/features/service-scripts.md)
## Lists of Things
-- [list of permissions](../lists-of-things/list-of-permissions.md)
+- [list of permissions](./doc/lists-of-things/list-of-permissions.md)
## Code-First Approach
@@ -20,21 +24,21 @@ If you prefer to understand a system by looking at the
first files which are invoked and starting from there,
here's a handy list!
-- [Kernel](../../src/Kernel.js), despite its intimidating name, is a
+- [Kernel](./src/Kernel.js), despite its intimidating name, is a
relatively simple (< 200 LOC) class which loads the modules
(modules register services), and then starts all the services.
-- [RuntimeEnvironment](../../src/boot/RuntimeEnvironment.js)
+- [RuntimeEnvironment](./src/boot/RuntimeEnvironment.js)
sets the configuration and runtime directories. It's invoked by Kernel.
- The default setup for running a self-hosted Puter loads these modules:
- - [CoreModule](../../src/CoreModule.js)
- - [DatabaseModule](../../src/DatabaseModule.js)
- - [LocalDiskStorageModule](../../src/LocalDiskStorageModule.js)
+ - [CoreModule](./src/CoreModule.js)
+ - [DatabaseModule](./src/DatabaseModule.js)
+ - [LocalDiskStorageModule](./src/LocalDiskStorageModule.js)
- HTTP endpoints are registered with
- [WebServerService](../../src/services/WebServerService.js)
+ [WebServerService](./src/services/WebServerService.js)
by these services:
- - [ServeGUIService](../../src/services/ServeGUIService.js)
- - [PuterAPIService](../../src/services/PuterAPIService.js)
- - [FilesystemAPIService](../../src/services/FilesystemAPIService.js)
+ - [ServeGUIService](./src/services/ServeGUIService.js)
+ - [PuterAPIService](./src/services/PuterAPIService.js)
+ - [FilesystemAPIService](./src/services/FilesystemAPIService.js)
## Development Philosophies
@@ -71,7 +75,7 @@ doing the useless work that reveals what the useful work is.
## Underlying Constructs
-- [putility's README.md](../../packages/putility/README.md)
+- [putility's README.md](../putility/README.md)
- Whenever you see `AdvancedBase`, that's from here
- Many things in backend extend this. Anything that doesn't only doesn't
because it was written before `AdvancedBase` existed.
diff --git a/src/backend/doc/Kernel.md b/src/backend/doc/Kernel.md
new file mode 100644
index 0000000000..0cb15ef05d
--- /dev/null
+++ b/src/backend/doc/Kernel.md
@@ -0,0 +1,65 @@
+# Puter Kernel Documentation
+
+## Overview
+
+The **Puter Kernel** is the core runtime component of the Puter system. It provides the foundational infrastructure for:
+
+- Initializing the runtime environment
+- Managing internal and external modules (extensions)
+- Setting up and booting core services
+- Configuring logging and debugging utilities
+- Integrating with third-party modules and performing dependency installs at runtime
+
+This kernel is responsible for orchestrating the startup sequence and ensuring that all necessary services, modules, and environmental configurations are properly loaded before the application enters its operational state.
+
+---
+
+## Features
+
+1. **Modular Architecture**:
+ The Kernel supports both internal and external modules:
+ - **Internal Modules**: Provided to Kernel by an initializing script, such
+ as `tools/run-selfhosted.js`, via the `add_module()` method.
+ - **External Modules**: Discovered in configured module directories and installed
+ dynamically. This includes resolving and executing `package.json` entries and
+ running `npm install` as needed.
+
+2. **Service Container & Registry**:
+ The Kernel initializes a service container that manages a wide range of services. Services can:
+ - Register modules
+ - Initialize dependencies
+ - Emit lifecycle events (`boot.consolidation`, `boot.activation`, `boot.ready`) to
+ orchestrate a stable and consistent environment.
+
+3. **Runtime Environment Setup**:
+ The Kernel sets up a `RuntimeEnvironment` to determine configuration paths and environment parameters. It also provides global helpers like `kv` for key-value storage and `cl` for simplified console logging.
+
+4. **Logging and Debugging**:
+ Uses a temporary `BootLogger` for the initialization phase until LogService is
+ initialized, at which point it will replace the boot logger. Debugging features
+ (`ll`, `xtra_log`) are enabled in development environments for convenience.
+
+## Initialization & Boot Process
+
+1. **Constructor**:
+ When a Kernel instance is created, it sets up basic parameters, initializes an empty
+ module list, and prepares `useapi()` integration.
+
+2. **Booting**:
+ The `boot()` method:
+ - Parses CLI arguments using `yargs`.
+ - Calls `_runtime_init()` to set up the `RuntimeEnvironment` and boot logger.
+ - Initializes global debugging/logging utilities.
+ - Sets up the service container (usually called `services`c instance of **Container**).
+ - Invokes module installation and service bootstrapping processes.
+
+3. **Module Installation**:
+ Internal modules are registered and installed first.
+ External modules are discovered, packaged, installed, and their code is executed.
+ External modules are given a special context with access to `useapi()`, a dynamic
+ import mechanism for Puter modules and extensions.
+
+4. **Service Bootstrapping**:
+ After modules and extensions are installed, services are initialized and activated.
+ For more information about how this works, see [boot-sequence.md](./contributors/boot-sequence.md).
+
diff --git a/src/backend/exports.js b/src/backend/exports.js
index 0ed27f9914..9ea422cc54 100644
--- a/src/backend/exports.js
+++ b/src/backend/exports.js
@@ -21,13 +21,17 @@ const { Kernel } = require("./src/Kernel.js");
const DatabaseModule = require("./src/DatabaseModule.js");
const LocalDiskStorageModule = require("./src/LocalDiskStorageModule.js");
const SelfHostedModule = require("./src/modules/selfhosted/SelfHostedModule.js");
-const PuterDriversModule = require("./src/PuterDriversModule.js");
const { testlaunch } = require("./src/index.js");
const BaseService = require("./src/services/BaseService.js");
const { Context } = require("./src/util/context.js");
const { TestDriversModule } = require("./src/modules/test-drivers/TestDriversModule.js");
const { PuterAIModule } = require("./src/modules/puterai/PuterAIModule.js");
const { BroadcastModule } = require("./src/modules/broadcast/BroadcastModule.js");
+const { WebModule } = require("./src/modules/web/WebModule.js");
+const { Core2Module } = require("./src/modules/core/Core2Module.js");
+const { TemplateModule } = require("./src/modules/template/TemplateModule.js");
+const { PuterFSModule } = require("./src/modules/puterfs/PuterFSModule.js");
+const { PerfMonModule } = require("./src/modules/perfmon/PerfMonModule.js");
module.exports = {
@@ -42,14 +46,25 @@ module.exports = {
Context,
Kernel,
+
+ EssentialModules: [
+ Core2Module,
+ PuterFSModule,
+ CoreModule,
+ WebModule,
+ TemplateModule,
+ ],
// Pre-built modules
CoreModule,
+ WebModule,
DatabaseModule,
- PuterDriversModule,
LocalDiskStorageModule,
SelfHostedModule,
TestDriversModule,
PuterAIModule,
BroadcastModule,
+
+ // Development modules
+ PerfMonModule,
};
diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js
index 7a4fb7690d..31f2fc37b8 100644
--- a/src/backend/src/CoreModule.js
+++ b/src/backend/src/CoreModule.js
@@ -1,3 +1,4 @@
+// METADATA // {"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
@@ -23,6 +24,16 @@ const { ProtectedAppES } = require("./om/entitystorage/ProtectedAppES");
const { Context } = require('./util/context');
+
+/**
+ * Core module for the Puter platform that includes essential services including
+ * authentication, filesystems, rate limiting, permissions, and various API endpoints.
+ *
+ * This is a monolithic module. Incrementally, services should be migrated to
+ * Core2Module and other modules instead. Core2Module has a smaller scope, and each
+ * new module will be a cohesive concern. Once CoreModule is empty, it will be removed
+ * and Core2Module will take on its name.
+ */
class CoreModule extends AdvancedBase {
dirname () { return __dirname; }
async install (context) {
@@ -33,11 +44,16 @@ class CoreModule extends AdvancedBase {
await install({ services, app, useapi, modapi });
}
- // Some services were created before the BaseService
- // class existed. They don't listen to the init event
- // and the order in which they're instantiated matters.
- // They all need to be installed after the init event
- // is dispatched, so they get a separate install method.
+ /**
+ * Installs legacy services that don't extend BaseService and require special handling.
+ * These services were created before the BaseService class existed and don't listen
+ * to the init event. They need to be installed after the init event is dispatched
+ * due to initialization order dependencies.
+ *
+ * @param {Object} context - The context object containing service references
+ * @param {Object} context.services - Service registry for registering legacy services
+ * @returns {Promise} Resolves when legacy services are installed
+ */
async install_legacy (context) {
const services = context.get('services');
await install_legacy({ services });
@@ -52,6 +68,9 @@ module.exports = CoreModule;
const install = async ({ services, app, useapi, modapi }) => {
const config = require('./config');
+
+ // === LIBRARIES ===
+
useapi.withuse(() => {
def('Service', require('./services/BaseService'));
def('Module', AdvancedBase);
@@ -68,7 +87,6 @@ const install = async ({ services, app, useapi, modapi }) => {
def('core.config', config);
});
- // === LIBRARIES ===
useapi.withuse(() => {
const ArrayUtil = require('./libraries/ArrayUtil');
services.registerService('util-array', ArrayUtil);
@@ -82,16 +100,11 @@ const install = async ({ services, app, useapi, modapi }) => {
// === SERVICES ===
// /!\ IMPORTANT /!\
- // For new services, put the import immediate above the
+ // For new services, put the import immediately above the
// call to services.registerService. We'll clean this up
// in a future PR.
- const { LogService } = require('./services/runtime-analysis/LogService');
- const { PagerService } = require('./services/runtime-analysis/PagerService');
- const { AlarmService } = require('./services/runtime-analysis/AlarmService');
- const { ErrorService } = require('./services/runtime-analysis/ErrorService');
const { CommandService } = require('./services/CommandService');
- const { ExpectationService } = require('./services/runtime-analysis/ExpectationService');
const { HTTPThumbnailService } = require('./services/thumbnails/HTTPThumbnailService');
const { PureJSThumbnailService } = require('./services/thumbnails/PureJSThumbnailService');
const { NAPIThumbnailService } = require('./services/thumbnails/NAPIThumbnailService');
@@ -124,12 +137,10 @@ const install = async ({ services, app, useapi, modapi }) => {
const { ESBuilder } = require('./om/entitystorage/ESBuilder');
const { Eq, Or } = require('./om/query/query');
const { TrackSpendingService } = require('./services/TrackSpendingService');
- const { ServerHealthService } = require('./services/runtime-analysis/ServerHealthService');
const { MakeProdDebuggingLessAwfulService } = require('./services/MakeProdDebuggingLessAwfulService');
const { ConfigurableCountingService } = require('./services/ConfigurableCountingService');
const { FSLockService } = require('./services/fs/FSLockService');
const { StrategizedService } = require('./services/StrategizedService');
- const WebServerService = require('./services/WebServerService');
const FilesystemAPIService = require('./services/FilesystemAPIService');
const ServeGUIService = require('./services/ServeGUIService');
const PuterAPIService = require('./services/PuterAPIService');
@@ -140,17 +151,10 @@ const install = async ({ services, app, useapi, modapi }) => {
// === Services which extend BaseService ===
services.registerService('system-validation', SystemValidationService);
- services.registerService('server-health', ServerHealthService);
- services.registerService('log-service', LogService);
services.registerService('commands', CommandService);
- services.registerService('web-server', WebServerService, { app });
services.registerService('__api-filesystem', FilesystemAPIService);
services.registerService('__api', PuterAPIService);
services.registerService('__gui', ServeGUIService);
- services.registerService('expectations', ExpectationService);
- services.registerService('pager', PagerService);
- services.registerService('alarm', AlarmService);
- services.registerService('error-service', ErrorService);
services.registerService('registry', RegistryService);
services.registerService('__registrant', RegistrantService);
services.registerService('fslock', FSLockService);
@@ -187,9 +191,6 @@ const install = async ({ services, app, useapi, modapi }) => {
]),
});
- const { ParameterService } = require('./services/ParameterService');
- services.registerService('params', ParameterService);
-
const { InformationService } = require('./services/information/InformationService');
services.registerService('information', InformationService)
@@ -316,9 +317,6 @@ const install = async ({ services, app, useapi, modapi }) => {
const { PermissionAPIService } = require('./services/PermissionAPIService');
services.registerService('__permission-api', PermissionAPIService);
- const { MountpointService } = require('./services/MountpointService');
- services.registerService('mountpoint', MountpointService);
-
const { AnomalyService } = require('./services/AnomalyService');
services.registerService('anomaly', AnomalyService);
@@ -351,29 +349,32 @@ const install = async ({ services, app, useapi, modapi }) => {
const { ReferralCodeService } = require('./services/ReferralCodeService');
services.registerService('referral-code', ReferralCodeService);
+
+ const { UserService } = require('./services/UserService');
+ services.registerService('user', UserService);
+
+ const { WSPushService } = require('./services/WSPushService');
+ services.registerService('__event-push-ws', WSPushService);
+
+ const { AppIconService } = require('./services/AppIconService');
+ services.registerService('app-icon', AppIconService);
}
const install_legacy = async ({ services }) => {
- const { ProcessEventService } = require('./services/runtime-analysis/ProcessEventService');
- // const { FilesystemService } = require('./filesystem/FilesystemService');
const PerformanceMonitor = require('./monitor/PerformanceMonitor');
const { OperationTraceService } = require('./services/OperationTraceService');
- const { WSPushService } = require('./services/WSPushService');
const { ClientOperationService } = require('./services/ClientOperationService');
const { EngPortalService } = require('./services/EngPortalService');
const { AppInformationService } = require('./services/AppInformationService');
const { FileCacheService } = require('./services/file-cache/FileCacheService');
// === Services which do not yet extend BaseService ===
- services.registerService('process-event', ProcessEventService);
// services.registerService('filesystem', FilesystemService);
services.registerService('operationTrace', OperationTraceService);
- services.registerService('__event-push-ws', WSPushService);
services.registerService('file-cache', FileCacheService);
services.registerService('client-operation', ClientOperationService);
services.registerService('app-information', AppInformationService);
services.registerService('engineering-portal', EngPortalService);
- // TODO: add to here: ResourceService and DatabaseFSEntryService
// This singleton was made before services existed,
// so we have to pass that to it manually
diff --git a/src/backend/src/Extension.js b/src/backend/src/Extension.js
index 6b2d4ade60..e3601d2b91 100644
--- a/src/backend/src/Extension.js
+++ b/src/backend/src/Extension.js
@@ -3,6 +3,10 @@ const EmitterFeature = require("@heyputer/putility/src/features/EmitterFeature")
const { Context } = require("./util/context");
const { ExtensionServiceState } = require("./ExtensionService");
+/**
+ * This class creates the `extension` global that is seem by Puter backend
+ * extensions.
+ */
class Extension extends AdvancedBase {
static FEATURES = [
EmitterFeature({
@@ -24,6 +28,9 @@ class Extension extends AdvancedBase {
console.log('Example method called by an extension.');
}
+ /**
+ * This will get a database instance from the default service.
+ */
get db () {
const db = this.service.values.get('db');
if ( ! db ) {
@@ -35,6 +42,12 @@ class Extension extends AdvancedBase {
return db;
}
+ /**
+ * This will create a GET endpoint on the default service.
+ * @param {*} path - route for the endpoint
+ * @param {*} handler - function to handle the endpoint
+ * @param {*} options - options like noauth (bool) and mw (array)
+ */
get (path, handler, options) {
// this extension will have a default service
this.ensure_service_();
@@ -51,6 +64,12 @@ class Extension extends AdvancedBase {
});
}
+ /**
+ * This will create a POST endpoint on the default service.
+ * @param {*} path - route for the endpoint
+ * @param {*} handler - function to handle the endpoint
+ * @param {*} options - options like noauth (bool) and mw (array)
+ */
post (path, handler, options) {
// this extension will have a default service
this.ensure_service_();
@@ -67,6 +86,13 @@ class Extension extends AdvancedBase {
});
}
+ /**
+ * This method will create the "default service" for an extension.
+ * This is specifically for Puter extensions that do not define their
+ * own service classes.
+ *
+ * @returns {void}
+ */
ensure_service_ () {
if ( this.service ) {
return;
diff --git a/src/backend/src/ExtensionService.js b/src/backend/src/ExtensionService.js
index 7ea96d12f1..8c867a9ce7 100644
--- a/src/backend/src/ExtensionService.js
+++ b/src/backend/src/ExtensionService.js
@@ -5,6 +5,11 @@ const configurable_auth = require("./middleware/configurable_auth");
const { Context } = require("./util/context");
const { DB_READ, DB_WRITE } = require("./services/database/consts");
+/**
+ * State shared with the default service and the `extension` global so that
+ * methods on `extension` can register routes (and make other changes in the
+ * future) to the default service.
+ */
class ExtensionServiceState extends AdvancedBase {
constructor (...a) {
super(...a);
@@ -75,6 +80,7 @@ class ExtensionService extends BaseService {
}
['__on_install.routes'] (_, { app }) {
+ if ( ! this.state ) debugger;
for ( const endpoint of this.state.endpoints_ ) {
endpoint.attach(app);
}
diff --git a/src/backend/src/Kernel.js b/src/backend/src/Kernel.js
index 1f7a5ecb7c..f99cafd31c 100644
--- a/src/backend/src/Kernel.js
+++ b/src/backend/src/Kernel.js
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase, libs } = require("@heyputer/putility");
const { Context } = require('./util/context');
const BaseService = require("./services/BaseService");
const useapi = require('useapi');
@@ -25,7 +25,8 @@ const { hideBin } = require('yargs/helpers');
const { Extension } = require("./Extension");
const { ExtensionModule } = require("./ExtensionModule");
const { spawn } = require("node:child_process");
-const { quot } = require("./util/strutil");
+
+const { quot } = libs.string;
class Kernel extends AdvancedBase {
constructor ({ entry_path } = {}) {
@@ -78,8 +79,6 @@ class Kernel extends AdvancedBase {
this._runtime_init({ args });
- // const express = require('express')
- // const app = express();
const config = require('./config');
globalThis.ll = o => o;
@@ -101,18 +100,11 @@ class Kernel extends AdvancedBase {
const { consoleLogManager } = require('./util/consolelog');
consoleLogManager.initialize_proxy_methods();
- // TODO: temporary dependency inversion; requires moving:
- // - rm, so we can move mv
- // - mv, so we can move mkdir
- // - generate_default_fsentries, so we can move mkdir
- // - mkdir, which needs an fs provider
-
// === START: Initialize Service Registry ===
const { Container } = require('./services/Container');
const services = new Container({ logger: this.bootLogger });
this.services = services;
- // app.set('services', services);
const root_context = Context.create({
environment: this.environment,
@@ -130,7 +122,6 @@ class Kernel extends AdvancedBase {
});
- // Error.stackTraceLimit = Infinity;
Error.stackTraceLimit = 200;
}
@@ -238,104 +229,93 @@ class Kernel extends AdvancedBase {
}
const mod_dirnames = fs.readdirSync(mods_dirpath);
for ( const mod_dirname of mod_dirnames ) {
- let mod_path = path_.join(mods_dirpath, mod_dirname);
+ await this.install_extern_mod_({
+ mod_install_root_context,
+ mod_dirname,
+ mod_path: path_.join(mods_dirpath, mod_dirname),
+ });
+ }
+ }
+ }
+
+ async install_extern_mod_({
+ mod_install_root_context,
+ mod_dirname,
+ mod_path,
+ }) {
+ const path_ = require('path');
+ const fs = require('fs');
- let stat = fs.lstatSync(mod_path);
- while ( stat.isSymbolicLink() ) {
- mod_path = fs.readlinkSync(mod_path);
- stat = fs.lstatSync(mod_path);
- }
+ let stat = fs.lstatSync(mod_path);
+ while ( stat.isSymbolicLink() ) {
+ mod_path = fs.readlinkSync(mod_path);
+ stat = fs.lstatSync(mod_path);
+ }
- if ( ! stat.isDirectory() && !(mod_dirname.endsWith('.js')) ) {
- continue;
- }
-
- const mod_name = path_.parse(mod_path).name;
- const mod_package_dir = `mod_packages/${mod_name}`;
- fs.mkdirSync(mod_package_dir);
-
- if ( ! stat.isDirectory() ) {
- this.create_mod_package_json(mod_package_dir, {
- name: mod_name,
- });
- fs.copyFileSync(mod_path, path_.join(mod_package_dir, 'main.js'));
- } else {
- if ( ! fs.existsSync(path_.join(mod_path, 'package.json')) ) {
- // Expect main.js or index.js to exist
- const options = ['main.js', 'index.js'];
- let entry_file = null;
- for ( const option of options ) {
- if ( fs.existsSync(path_.join(mod_path, option)) ) {
- entry_file = option;
- break;
- }
- }
- if ( ! entry_file ) {
- // If directory is empty, we'll just skip it
- if ( fs.readdirSync(mod_path).length === 0 ) {
- this.bootLogger.warn(`Empty mod directory ${quot(mod_path)}; skipping...`);
- continue;
- }
-
- // Other wise, we'll throw an error
- this.bootLogger.error(`Expected main.js or index.js in ${quot(mod_path)}`);
- if ( ! process.env.SKIP_INVALID_MODS ) {
- this.bootLogger.error(`Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.`);
- process.exit(1);
- } else {
- continue;
- }
- }
-
- this.create_mod_package_json(mod_package_dir, {
- name: mod_name,
- entry: entry_file,
- });
- }
- fs.cpSync(mod_path, mod_package_dir, {
- recursive: true,
- });
- }
-
- const mod_require_dir = path_.join(process.cwd(), mod_package_dir);
-
- await this.run_npm_install(mod_require_dir);
-
- const mod = new ExtensionModule();
- mod.extension = new Extension();
-
- // This is where the module gets the 'use' and 'def' globals
- await this.useapi.awithuse(async () => {
- // This is where the module gets the 'extension' global
- await useapi.aglobalwith({
- extension: mod.extension,
- }, async () => {
- const maybe_promise = require(mod_require_dir);
- if ( maybe_promise && maybe_promise instanceof Promise ) {
- await maybe_promise;
- }
- });
- });
+ // Mod must be a directory or javascript file
+ if ( ! stat.isDirectory() && !(mod_path.endsWith('.js')) ) {
+ return;
+ }
+
+ const mod_name = path_.parse(mod_path).name;
+ const mod_package_dir = `mod_packages/${mod_name}`;
+ fs.mkdirSync(mod_package_dir);
+
+ if ( ! stat.isDirectory() ) {
+ this.create_mod_package_json(mod_package_dir, {
+ name: mod_name,
+ entry: 'main.js'
+ });
+ fs.copyFileSync(mod_path, path_.join(mod_package_dir, 'main.js'));
+ } else {
+ // If directory is empty, we'll just skip it
+ if ( fs.readdirSync(mod_path).length === 0 ) {
+ this.bootLogger.warn(`Empty mod directory ${quot(mod_path)}; skipping...`);
+ return;
+ }
- const mod_context = this._create_mod_context(mod_install_root_context, {
- name: mod_dirname,
- ['module']: mod,
- external: true,
- mod_path,
- });
-
- // TODO: DRY `awithuse` and `aglobalwith` with above
- await this.useapi.awithuse(async () => {
- await useapi.aglobalwith({
- extension: mod.extension,
- }, async () => {
- // This is where the 'install' event gets triggered
- await mod.install(mod_context);
- });
+ // Create package.json if it doesn't exist
+ if ( ! fs.existsSync(path_.join(mod_path, 'package.json')) ) {
+ this.create_mod_package_json(mod_package_dir, {
+ name: mod_name,
});
}
+
+ // Copy mod contents to `/mod_packages`
+ fs.cpSync(mod_path, mod_package_dir, {
+ recursive: true,
+ });
}
- }
+
+ const mod_require_dir = path_.join(process.cwd(), mod_package_dir);
+
+ await this.run_npm_install(mod_require_dir);
+
+ const mod = new ExtensionModule();
+ mod.extension = new Extension();
+
+ const mod_context = this._create_mod_context(mod_install_root_context, {
+ name: mod_dirname,
+ ['module']: mod,
+ external: true,
+ mod_path,
+ });
+
+ // This is where the module gets the 'use' and 'def' globals
+ await this.useapi.awithuse(async () => {
+ // This is where the module gets the 'extension' global
+ await useapi.aglobalwith({
+ extension: mod.extension,
+ }, async () => {
+ const maybe_promise = require(mod_require_dir);
+ if ( maybe_promise && maybe_promise instanceof Promise ) {
+ await maybe_promise;
+ }
+ // This is where the 'install' event gets triggered
+ await mod.install(mod_context);
+ });
+ });
+ };
_create_mod_context (parent, options) {
const path_ = require('path');
@@ -380,6 +360,30 @@ class Kernel extends AdvancedBase {
const fs = require('fs');
const path_ = require('path');
+ // Expect main.js or index.js to exist
+ const options = ['main.js', 'index.js'];
+
+ // If no entry specified, find file with conventional name
+ if ( ! entry ) {
+ for ( const option of options ) {
+ if ( fs.existsSync(path_.join(mod_path, option)) ) {
+ entry = option;
+ break;
+ }
+ }
+ }
+
+ // If no entry specified or found, skip or error
+ if ( ! entry ) {
+ this.bootLogger.error(`Expected main.js or index.js in ${quot(mod_path)}`);
+ if ( ! process.env.SKIP_INVALID_MODS ) {
+ this.bootLogger.error(`Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.`);
+ process.exit(1);
+ } else {
+ return;
+ }
+ }
+
const data = JSON.stringify({
name,
version: '1.0.0',
diff --git a/src/backend/src/PuterDriversModule.js b/src/backend/src/PuterDriversModule.js
deleted file mode 100644
index 7cb6e55e52..0000000000
--- a/src/backend/src/PuterDriversModule.js
+++ /dev/null
@@ -1,31 +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 .
- */
-const { AdvancedBase } = require("@heyputer/putility");
-
-class PuterDriversModule extends AdvancedBase {
- async install () {}
- async install_legacy (context) {
- const services = context.get('services');
-
- const { DriverService } = require("./services/drivers/DriverService");
- services.registerService('driver', DriverService);
- }
-}
-
-module.exports = PuterDriversModule;
diff --git a/src/backend/src/api/APIError.js b/src/backend/src/api/APIError.js
index d8c678dc91..eb16f341d4 100644
--- a/src/backend/src/api/APIError.js
+++ b/src/backend/src/api/APIError.js
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
const { URLSearchParams } = require("node:url");
-const { quot } = require("../util/strutil");
+const { quot } = require('@heyputer/putility').libs.string;
/**
* APIError represents an error that can be sent to the client.
@@ -324,36 +324,6 @@ module.exports = class APIError {
message: () => 'Invalid token.',
},
- // drivers
- 'interface_not_found': {
- status: 404,
- message: ({ interface_name }) => `Interface not found: ${quot(interface_name)}`,
- },
- 'no_implementation_available': {
- status: 502,
- message: ({
- iface,
- interface_name,
- driver
- }) => `No implementation available for ` +
- (iface ?? interface_name) ? 'interface' : 'driver' +
- ' ' + quot(iface ?? interface_name ?? driver) + '.',
- },
- 'method_not_found': {
- status: 404,
- message: ({ interface_name, method_name }) => `Method not found: ${quot(method_name)} on interface ${quot(interface_name)}`,
- },
- 'missing_required_argument': {
- status: 400,
- message: ({ interface_name, method_name, arg_name }) =>
- `Missing required argument ${quot(arg_name)} for method ${quot(method_name)} on interface ${quot(interface_name)}`,
- },
- 'argument_consolidation_failed': {
- status: 400,
- message: ({ interface_name, method_name, arg_name, message }) =>
- `Failed to parse or process argument ${quot(arg_name)} for method ${quot(method_name)} on interface ${quot(interface_name)}: ${message}`,
- },
-
// SLA
'rate_limit_exceeded': {
status: 429,
@@ -505,18 +475,6 @@ module.exports = class APIError {
status: 400,
message: 'Incorrect or missing anti-CSRF token.',
},
-
- // Chat
- // TODO: specifying these errors here might be a violation
- // of separation of concerns. Services could register their
- // own errors with an error registry.
- 'max_tokens_exceeded': {
- status: 400,
- message: ({ input_tokens, max_tokens }) =>
- `Input exceeds maximum token count. ` +
- `Input has ${input_tokens} tokens, ` +
- `but the maximum is ${max_tokens}.`,
- },
};
/**
diff --git a/src/backend/src/api/eggspress.js b/src/backend/src/api/eggspress.js
index fca0bbf98c..5840ef9fd8 100644
--- a/src/backend/src/api/eggspress.js
+++ b/src/backend/src/api/eggspress.js
@@ -1,198 +1,2 @@
-/*
- * 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 .
- */
-const express = require('express');
-const multer = require('multer');
-const multest = require('@heyputer/multest');
-const api_error_handler = require('../api/api_error_handler.js');
-
-const fsBeforeMW = require('../middleware/fs');
-const APIError = require('./APIError.js');
-const { Context } = require('../util/context.js');
-
-/**
- * eggspress() is a factory function for creating express routers.
- *
- * @param {*} route the route to the router
- * @param {*} settings the settings for the router. The following
- * properties are supported:
- * - auth: whether or not to use the auth middleware
- * - fs: whether or not to use the fs middleware
- * - json: whether or not to use the json middleware
- * - customArgs: custom arguments to pass to the router
- * - allowedMethods: the allowed HTTP methods
- * @param {*} handler the handler for the router
- * @returns {express.Router} the router
- */
-module.exports = function eggspress (route, settings, handler) {
- const router = express.Router();
- const mw = [];
- const afterMW = [];
-
- // These flags enable specific middleware.
- if ( settings.abuse ) mw.push(require('../middleware/abuse')(settings.abuse));
- if ( settings.auth ) mw.push(require('../middleware/auth'));
- if ( settings.auth2 ) mw.push(require('../middleware/auth2'));
- if ( settings.fs ) {
- mw.push(fsBeforeMW);
- }
- if ( settings.verified ) mw.push(require('../middleware/verified'));
- if ( settings.json ) mw.push(express.json());
-
- // The `files` setting is an array of strings. Each string is the name
- // of a multipart field that contains files. `multer` is used to parse
- // the multipart request and store the files in `req.files`.
- if ( settings.files ) {
- for ( const key of settings.files ) {
- mw.push(multer().array(key));
- }
- }
-
- if ( settings.multest ) {
- mw.push(multest());
- }
-
- // The `multipart_jsons` setting is an array of strings. Each string
- // is the name of a multipart field that contains JSON. This middleware
- // parses the JSON in each field and stores the result in `req.body`.
- if ( settings.multipart_jsons ) {
- for ( const key of settings.multipart_jsons ) {
- mw.push((req, res, next) => {
- try {
- if ( ! Array.isArray(req.body[key]) ) {
- req.body[key] = [JSON.parse(req.body[key])];
- } else {
- req.body[key] = req.body[key].map(JSON.parse);
- }
- } catch (e) {
- return res.status(400).send({
- error: {
- message: `Invalid JSON in multipart field ${key}`
- }
- });
- }
- next();
- });
- }
- }
-
- // The `alias` setting is an object. Each key is the name of a
- // parameter. Each value is the name of a parameter that should
- // be aliased to the key.
- if ( settings.alias ) {
- for ( const alias in settings.alias ) {
- const target = settings.alias[alias];
- mw.push((req, res, next) => {
- const values = req.method === 'GET' ? req.query : req.body;
- if ( values[alias] ) {
- values[target] = values[alias];
- }
- next();
- });
- }
- }
-
- // The `parameters` setting is an object. Each key is the name of a
- // parameter. Each value is a `Param` object. The `Param` object
- // specifies how to validate the parameter.
- if ( settings.parameters ) {
- for ( const key in settings.parameters ) {
- const param = settings.parameters[key];
- mw.push(async (req, res, next) => {
- if ( ! req.values ) req.values = {};
-
- const values = req.method === 'GET' ? req.query : req.body;
- const getParam = (key) => values[key];
- try {
- const result = await param.consolidate({ req, getParam });
- req.values[key] = result;
- } catch (e) {
- api_error_handler(e, req, res, next);
- return;
- }
- next();
- });
- }
- }
-
- // what if I wanted to pass arguments to, for example, `json`?
- if ( settings.customArgs ) mw.push(settings.customArgs);
-
- if ( settings.alarm_timeout ) {
- mw.push((req, res, next) => {
- setTimeout(() => {
- if ( ! res.headersSent ) {
- const log = req.services.get('log-service').create('eggspress:timeout');
- const errors = req.services.get('error-service').create(log);
- let id = Array.isArray(route) ? route[0] : route;
- id = id.replace(/\//g, '_');
- errors.report(id, {
- source: new Error('Response timed out.'),
- message: 'Response timed out.',
- trace: true,
- alarm: true,
- });
- }
- }, settings.alarm_timeout);
- next();
- });
- }
-
- if ( settings.response_timeout ) {
- mw.push((req, res, next) => {
- setTimeout(() => {
- if ( ! res.headersSent ) {
- api_error_handler(APIError.create('response_timeout'), req, res, next);
- }
- }, settings.response_timeout);
- next();
- });
- }
-
- if ( settings.mw ) mw.push(...settings.mw);
-
- const errorHandledHandler = async function (req, res, next) {
- if ( settings.subdomain ) {
- if ( require('../helpers').subdomain(req) !== settings.subdomain ) {
- return next();
- }
- }
- try {
- const expected_ctx = res.locals.ctx;
- const received_ctx = Context.get(undefined, { allow_fallback: true });
-
- if ( expected_ctx != received_ctx ) {
- await expected_ctx.arun(async () => {
- await handler(req, res, next);
- });
- } else await handler(req, res, next);
- } catch (e) {
- api_error_handler(e, req, res, next);
- }
- };
-
- if ( settings.allowedMethods.includes('GET') ) {
- router.get(route, ...mw, errorHandledHandler, ...afterMW);
- }
-
- if ( settings.allowedMethods.includes('POST') ) {
- router.post(route, ...mw, errorHandledHandler, ...afterMW);
- }
-
- return router;
-}
\ No newline at end of file
+// This file is a legacy alias
+module.exports = require('../modules/web/lib/eggspress.js');
diff --git a/src/backend/src/api/filesystem/UserParam.js b/src/backend/src/api/filesystem/UserParam.js
index 563824a8f2..75ff1fcf44 100644
--- a/src/backend/src/api/filesystem/UserParam.js
+++ b/src/backend/src/api/filesystem/UserParam.js
@@ -17,9 +17,6 @@
* along with this program. If not, see .
*/
module.exports = class UserParam {
- constructor () {
- //
- }
consolidate ({ req }) {
return req.user;
}
diff --git a/src/backend/src/boot/RuntimeEnvironment.js b/src/backend/src/boot/RuntimeEnvironment.js
index 4d30eb1316..09a88119b0 100644
--- a/src/backend/src/boot/RuntimeEnvironment.js
+++ b/src/backend/src/boot/RuntimeEnvironment.js
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
const { AdvancedBase } = require("@heyputer/putility");
-const { quot } = require("../util/strutil");
+const { quot } = require('@heyputer/putility').libs.string;
const { TechnicalError } = require("../errors/TechnicalError");
const { print_error_help } = require("../errors/error_help_details");
const default_config = require("./default_config");
@@ -93,8 +93,7 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
throw new Error(`No valid config file found in path: ${path}`);
},
env_not_set: name => () => {
- if ( process.env[name] ) return false;
- return true;
+ return ! process.env[name];
}
});
@@ -233,18 +232,14 @@ class RuntimeEnvironment extends AdvancedBase {
]
);
+ // Note: there used to be a 'mods_path_entry' here too
+ // but it was never used
const pwd_path_entry = this.get_first_suitable_path_(
{ pathFor: 'working directory' },
this.runtime_paths,
[ this.path_checks.require_write_permission ]
);
- const mods_path_entry = this.get_first_suitable_path_(
- { pathFor: 'mods', optional: true },
- this.mod_paths,
- [ this.path_checks.require_read_permission ],
- );
-
process.chdir(pwd_path_entry.path);
// Check for a valid config file in the config path
@@ -287,9 +282,8 @@ class RuntimeEnvironment extends AdvancedBase {
);
const config_values = JSON.parse(config_raw);
for ( const k in generated_values ) {
- if ( config_values[k] ) {
- generated_values[k] = config_values[k];
- }
+ if ( ! config_values[k] ) continue;
+ generated_values[k] = config_values[k];
}
}
}
@@ -334,9 +328,6 @@ class RuntimeEnvironment extends AdvancedBase {
throw new Error('config_name is required');
}
this.logger.info(hl(`config name`) + ` ${quot(config.config_name)}`);
- // console.log(config.services);
- // console.log(Object.keys(config.services));
- // console.log({ ...config.services });
const mod_paths = [];
environment.mod_paths = mod_paths;
@@ -363,12 +354,13 @@ class RuntimeEnvironment extends AdvancedBase {
}
get_first_suitable_path_ (meta, paths, last_checks) {
- iter_paths:
for ( const entry of paths ) {
const checks = [...(entry.checks ?? []), ...last_checks];
this.logger.info(
`Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...`
);
+
+ let checks_pass = true;
for ( const check of checks ) {
this.logger.info(
`-> doing ${quot(check.name)} on path ${quot(entry.path)}...`
@@ -378,9 +370,12 @@ class RuntimeEnvironment extends AdvancedBase {
this.logger.info(
`-> ${quot(check.name)} doesn't like this path`
);
- continue iter_paths;
+ checks_pass = false;
+ break;
}
}
+
+ if ( ! checks_pass ) continue;
this.logger.info(
`${hl('USING')} ${quot(entry.path)} for ${meta.pathFor}.`
diff --git a/src/backend/src/codex/CodeModel.js b/src/backend/src/codex/CodeModel.js
deleted file mode 100644
index d771621ca0..0000000000
--- a/src/backend/src/codex/CodeModel.js
+++ /dev/null
@@ -1,25 +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 CodeModel {
- static create () {}
-}
-
-module.exports = {
- CodeModel,
-};
diff --git a/src/backend/src/codex/Sequence.js b/src/backend/src/codex/Sequence.js
index 86e981a852..f6db2a1a92 100644
--- a/src/backend/src/codex/Sequence.js
+++ b/src/backend/src/codex/Sequence.js
@@ -109,7 +109,7 @@ class Sequence {
async run (values) {
// Initialize scope
values = values || this.thisArg?.values || {};
- this.scope_.__proto__ = values;
+ Object.setPrototypeOf(this.scope_, values);
// Run sequence
for ( ; this.i < this.steps.length ; this.i++ ) {
@@ -126,9 +126,8 @@ class Sequence {
const parent_scope = this.scope_;
this.scope_ = {};
// We could do Object.assign(this.scope_, parent_scope), but
- // setting __proto__ is faster because it leverages the optimizations
- // of the JS engine for the prototype chain.
- this.scope_.__proto__ = parent_scope;
+ // setting the prototype should be faster (in theory)
+ Object.setPrototypeOf(this.scope_, parent_scope);
if ( this.sequence_.options_.record_history ) {
this.value_history_.push(this.scope_);
@@ -142,6 +141,10 @@ class Sequence {
this.thisArg, this,
);
+ if ( this.last_return_ instanceof Sequence.SequenceState ) {
+ this.scope_ = this.last_return_.scope_;
+ }
+
if ( this.sequence_.options_.after_each ) {
await this.sequence_.options_.after_each(this, step);
}
diff --git a/src/backend/src/config.js b/src/backend/src/config.js
index 16eebe5995..06662efb0f 100644
--- a/src/backend/src/config.js
+++ b/src/backend/src/config.js
@@ -31,24 +31,24 @@ config.disable_temp_users = false;
config.default_user_group = '78b1b1dd-c959-44d2-b02c-8735671f9997';
config.default_temp_group = 'b7220104-7905-4985-b996-649fdcdb3c8f';
-config.max_file_size = 100_000_000_000,
-config.max_thumb_size = 1_000,
-config.max_fsentry_name_length = 767,
+config.max_file_size = 100_000_000_000;
+config.max_thumb_size = 1_000;
+config.max_fsentry_name_length = 767;
-config.username_regex = /^\w{1,}$/;
+config.username_regex = /^\w+$/;
config.username_max_length = 45;
-config.subdomain_regex = /^[a-zA-Z0-9-_-]+$/;
+config.subdomain_regex = /^[a-zA-Z0-9_-]+$/;
config.subdomain_max_length = 60;
-config.app_name_regex = /^[a-zA-Z0-9-_-]+$/;
+config.app_name_regex = /^[a-zA-Z0-9_-]+$/;
config.app_name_max_length = 60;
config.app_title_max_length = 60;
config.min_pass_length = 6;
-config.strict_email_verification_required = false,
-config.require_email_verification_to_publish_website = false,
+config.strict_email_verification_required = false;
+config.require_email_verification_to_publish_website = false;
-config.kv_max_key_size = 1024,
-config.kv_max_value_size = 400 * 1024,
+config.kv_max_key_size = 1024;
+config.kv_max_value_size = 400 * 1024;
config.monitor = {
metricsInterval: 60000,
@@ -70,9 +70,6 @@ config.app_max_icon_size = 5*1024*1024;
config.defaultjs_asset_path = '../../';
-// config.origin = config.protocol + '://' + config.domain;
-// config.api_base_url = config.protocol + '://api.' + config.domain;
-// config.social_card = `${config.origin}/assets/img/screenshot.png`;
config.short_description = `Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.`;
config.title = 'Puter';
config.company = 'Puter Technologies Inc.';
@@ -96,11 +93,12 @@ config.reserved_words = [];
// set default S3 settings for this server, if any
if (config.server_id) {
// see if this server has a specific bucket
- for (let index = 0; index < config.servers.length; index++) {
- if (config.servers[index].id === config.server_id && config.servers[index].s3_bucket){
- config.s3_bucket = config.servers[index].s3_bucket;
- config.s3_region = config.servers[index].region;
- }
+ for ( const server of config.servers ) {
+ if ( server.id !== config.server_id ) continue;
+ if ( ! server.s3_bucket ) continue;
+
+ config.s3_bucket = server.s3_bucket;
+ config.s3_region = server.region;
}
}
@@ -142,15 +140,15 @@ if ( config.os.refined ) {
module.exports = config;
// NEW_CONFIG_LOADING
+const maybe_port = config =>
+ config.pub_port !== 80 && config.pub_port !== 443 ? ':' + config.pub_port : '';
const computed_defaults = {
pub_port: config => config.http_port,
- origin: config => config.protocol + '://' + config.domain +
- (config.pub_port !== 80 && config.pub_port !== 443 ? ':' + config.pub_port : ''),
+ origin: config => config.protocol + '://' + config.domain + maybe_port(config),
api_base_url: config => config.experimental_no_subdomain
? config.origin
- : config.protocol + '://api.' + config.domain +
- (config.pub_port !== 80 && config.pub_port !== 443 ? ':' + config.pub_port : ''),
+ : config.protocol + '://api.' + config.domain + maybe_port(config),
social_card: config => `${config.origin}/assets/img/screenshot.png`,
};
@@ -162,7 +160,7 @@ let config_to_export;
// load_config() may replace
const config_pointer = {};
{
- config_pointer.__proto__ = config;
+ Object.setPrototypeOf(config_pointer, config);
config_to_export = config_pointer;
}
@@ -173,15 +171,14 @@ const config_pointer = {};
let replacement_config = {
...o,
};
- // replacement_config.__proto__ = config_pointer.__proto__;
- replacement_config = deep_proto_merge(replacement_config, config_pointer.__proto__, {
+ replacement_config = deep_proto_merge(replacement_config, Object.getPrototypeOf(config_pointer), {
preserve_flag: true,
})
- config_pointer.__proto__ = replacement_config;
+ Object.setPrototypeOf(config_pointer, replacement_config);
};
const config_api = { load_config };
- config_api.__proto__ = config_to_export;
+ Object.setPrototypeOf(config_api, config_to_export);
config_to_export = config_api;
}
@@ -212,7 +209,7 @@ const config_pointer = {};
const config_runtime_values = {
$: 'runtime-values'
};
- config_runtime_values.__proto__ = config_to_export;
+ Object.setPrototypeOf(config_runtime_values, config_to_export);
config_to_export = config_runtime_values
// These can be difficult to find and cause painful
diff --git a/src/backend/src/config/ConfigLoader.js b/src/backend/src/config/ConfigLoader.js
index 0bb2bd76c8..516a13e3fc 100644
--- a/src/backend/src/config/ConfigLoader.js
+++ b/src/backend/src/config/ConfigLoader.js
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
const { AdvancedBase } = require("@heyputer/putility");
-const { quot } = require("../util/strutil");
+const { quot } = require('@heyputer/putility').libs.string;
class ConfigLoader extends AdvancedBase {
static MODULES = {
diff --git a/src/backend/src/data/hardcoded-permissions.js b/src/backend/src/data/hardcoded-permissions.js
index abaa2940b4..0d81513ccb 100644
--- a/src/backend/src/data/hardcoded-permissions.js
+++ b/src/backend/src/data/hardcoded-permissions.js
@@ -84,17 +84,20 @@ const hardcoded_user_group_permissions = {
'service:hello-world:ii:hello-world': policy_perm('temp.es'),
'service:puter-kvstore:ii:puter-kvstore': policy_perm('temp.kv'),
'driver:puter-kvstore': policy_perm('temp.kv'),
- 'driver:puter-notifications': policy_perm('temp.es'),
- 'driver:puter-apps': policy_perm('temp.es'),
- 'driver:puter-subdomains': policy_perm('temp.es'),
+ 'service:puter-notifications:ii:crud-q': policy_perm('temp.es'),
+ 'service:puter-apps:ii:crud-q': policy_perm('temp.es'),
+ 'service:puter-subdomains:ii:crud-q': policy_perm('temp.es'),
+ 'service:es\\Cnotification:ii:crud-q': policy_perm('user.es'),
+ 'service:es\\Capp:ii:crud-q': policy_perm('user.es'),
+ 'service:es\\Csubdomain:ii:crud-q': policy_perm('user.es'),
},
'78b1b1dd-c959-44d2-b02c-8735671f9997': {
'service:hello-world:ii:hello-world': policy_perm('user.es'),
'service:puter-kvstore:ii:puter-kvstore': policy_perm('user.kv'),
'driver:puter-kvstore': policy_perm('user.kv'),
- 'driver:puter-notifications': policy_perm('user.es'),
- 'driver:puter-apps': policy_perm('user.es'),
- 'driver:puter-subdomains': policy_perm('user.es'),
+ 'service:es\\Cnotification:ii:crud-q': policy_perm('user.es'),
+ 'service:es\\Capp:ii:crud-q': policy_perm('user.es'),
+ 'service:es\\Csubdomain:ii:crud-q': policy_perm('user.es'),
},
},
};
diff --git a/src/backend/src/definitions/Driver.js b/src/backend/src/definitions/Driver.js
deleted file mode 100644
index 4bf79af524..0000000000
--- a/src/backend/src/definitions/Driver.js
+++ /dev/null
@@ -1,192 +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 .
- */
-const { AdvancedBase } = require("@heyputer/putility");
-const { Context } = require('../util/context')
-const APIError = require("../api/APIError");
-const { AppUnderUserActorType, UserActorType } = require("../services/auth/Actor");
-const { BaseOperation } = require("../services/OperationTraceService");
-const { CodeUtil } = require("../codex/CodeUtil");
-
-/**
- * Base class for all driver implementations.
- *
- * @deprecated - we use traits on services now. This class is kept for compatibility
- * with EntityStoreImplementation and DBKVStore which still use this.
- */
-class Driver extends AdvancedBase {
- constructor (...a) {
- super(...a);
- const methods = this._get_merged_static_object('METHODS');
- // Turn each method into an operation
- for ( const k in methods ) {
- methods[k] = CodeUtil.mrwrap(methods[k], BaseOperation, {
- name: `${this.constructor.ID}:${k}`,
- });
- };
- this.methods = methods;
- this.sla = this._get_merged_static_object('SLA');
- }
-
- async call (method, args) {
- if ( ! this.methods[method] ) {
- throw new Error(`method not found: ${method}`);
- }
-
- const pseudo_this = Object.assign({}, this);
-
- const context = Context.get();
- pseudo_this.context = context;
- pseudo_this.services = context.get('services');
- const services = context.get('services');
- pseudo_this.log = services.get('log-service').create(this.constructor.name);
-
- await this._sla_enforcement(method);
-
- return await this.methods[method].call(pseudo_this, args);
- }
-
- async _sla_enforcement (method) {
- const context = Context.get();
- const services = context.get('services');
- const method_key = `${this.constructor.ID}:${method}`;
- const svc_sla = services.get('sla');
-
- // System SLA enforcement
- {
- const sla_key = `driver:impl:${method_key}`;
- const sla = await svc_sla.get('system', sla_key);
-
- const sys_method_key = `system:${method_key}`;
-
- // short-term rate limiting
- if ( sla?.rate_limit ) {
- const svc_rateLimit = services.get('rate-limit');
- let eventual_success = false;
- for ( let i = 0 ; i < 60 ; i++ ) {
- try {
- await svc_rateLimit.check_and_increment(sys_method_key, sla.rate_limit.max, sla.rate_limit.period);
- eventual_success = true;
- break;
- } catch ( e ) {
- if (
- ! ( e instanceof APIError ) ||
- e.fields.code !== 'rate_limit_exceeded'
- ) throw e;
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
- }
- if ( ! eventual_success ) {
- throw APIError.create('server_rate_exceeded');
- }
- }
- }
-
- // test_mode is checked to prevent rate limiting when it is enabled
- const test_mode = context.get('test_mode');
-
- // User SLA enforcement
- {
- const actor = context.get('actor').get_related_actor(UserActorType);
-
- const user_is_verified = !! actor.type.user.email_confirmed;
-
- const sla_key = `driver:impl:${method_key}`;
- const sla = await svc_sla.get(
- user_is_verified ? 'user_verified' : 'user_unverified',
- sla_key
- );
-
- const user_method_key = `actor:${actor.uid}:${method_key}`;
-
- // short-term rate limiting
- if ( sla?.rate_limit ) {
- const svc_rateLimit = services.get('rate-limit');
- await svc_rateLimit.check_and_increment(method_key, sla.rate_limit.max, sla.rate_limit.period);
- }
-
- // long-term rate limiting
- if ( sla?.monthly_limit && ! test_mode ) {
- const svc_monthlyUsage = services.get('monthly-usage');
- const count = await svc_monthlyUsage.check(
- actor, {
- 'driver.interface': this.constructor.INTERFACE,
- 'driver.implementation': this.constructor.ID,
- 'driver.method': method,
- });
- if ( count >= sla.monthly_limit ) {
- throw APIError.create('monthly_limit_exceeded', null, {
- method_key,
- limit: sla.monthly_limit,
- });
- }
- }
- }
-
- // App SLA enforcement
- await (async () => {
- const actor = context.get('actor');
- if ( ! ( actor.type instanceof AppUnderUserActorType ) ) return;
-
- const sla_key = `driver:impl:${method_key}`;
- const sla = await svc_sla.get('app_default', sla_key);
-
- // long-term rate limiting
- if ( sla?.monthly_limit && ! test_mode ) {
- const svc_monthlyUsage = services.get('monthly-usage');
- const count = await svc_monthlyUsage.check(
- actor, {
- 'driver.interface': this.constructor.INTERFACE,
- 'driver.implementation': this.constructor.ID,
- 'driver.method': method,
- });
- if ( count >= sla.monthly_limit ) {
- throw APIError.create('monthly_limit_exceeded', null, {
- method_key,
- limit: sla.monthly_limit,
- });
- }
- }
- })();
-
- // Record monthly usage
- if ( ! test_mode ) {
- const actor = context.get('actor');
- const svc_monthlyUsage = services.get('monthly-usage');
- const extra = {
- 'driver.interface': this.constructor.INTERFACE,
- 'driver.implementation': this.constructor.ID,
- 'driver.method': method,
- ...(this.get_usage_extra ? this.get_usage_extra() : {}),
- };
- await svc_monthlyUsage.increment(actor, method_key, extra);
- }
- }
-
- async get_response_meta () {
- return {
- driver: this.constructor.ID,
- driver_version: this.constructor.VERSION,
- driver_interface: this.constructor.INTERFACE,
- };
- }
-}
-
-module.exports = {
- Driver,
-};
diff --git a/src/backend/src/drivers/DBKVStore.js b/src/backend/src/drivers/DBKVStore.js
deleted file mode 100644
index 59b994bb6c..0000000000
--- a/src/backend/src/drivers/DBKVStore.js
+++ /dev/null
@@ -1,217 +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 .
- */
-const config = require("../config");
-const APIError = require("../api/APIError");
-const { DB_READ, DB_WRITE } = require("../services/database/consts");
-const { Driver } = require("../definitions/Driver");
-const { get_app } = require("../helpers");
-
-class DBKVStore extends Driver {
- static ID = 'public-db-kvstore';
- static VERSION = '0.0.0';
- static INTERFACE = 'puter-kvstore';
- static MODULES = {
- murmurhash: require('murmurhash'),
- }
- static METHODS = {
- get: async function ({ app_uid, key }) {
- const actor = this.context.get('actor');
-
- // If the actor is an app then it gets its own KV store.
- // The way this is implemented isn't ideal for future behaviour;
- // a KV implementation specified by the user would have parameters
- // that are scoped to the app, so this should eventually be
- // changed to get the app ID from the same interface that would
- // be used to obtain per-app user-specified implementation params.
- let app = actor.type?.app ?? undefined;
- const user = actor.type?.user ?? undefined;
-
- if ( ! user ) throw new Error('User not found');
-
- if ( ! app && app_uid ) {
- app = await get_app({ uid: app_uid });
- }
-
- const db = this.services.get('database').get(DB_READ, 'kvstore');
- const key_hash = this.modules.murmurhash.v3(key);
- const kv = app ? await db.read(
- `SELECT * FROM kv WHERE user_id=? AND app=? AND kkey_hash=? LIMIT 1`,
- [ user.id, app.uid, key_hash ]
- ) : await db.read(
- `SELECT * FROM kv WHERE user_id=? AND (app IS NULL OR app = 'global') AND kkey_hash=? LIMIT 1`,
- [ user.id, key_hash ]
- );
-
- if ( kv[0] ) kv[0].value = db.case({
- mysql: () => kv[0].value,
- otherwise: () => JSON.parse(kv[0].value ?? 'null'),
- })();
-
- return kv[0]?.value ?? null;
- },
- set: async function ({ app_uid, key, value }) {
- const actor = this.context.get('actor');
-
- // Validate the key
- // get() doesn't String() the key but it only passes it to
- // murmurhash.v3() so it doesn't need to ¯\_(ツ)_/¯
- key = String(key);
- if ( Buffer.byteLength(key, 'utf8') > config.kv_max_key_size ) {
- throw new Error(`key is too large. Max size is ${config.kv_max_key_size}.`);
- }
-
- // Validate the value
- value = value === undefined ? null : value;
- if (
- value !== null &&
- Buffer.byteLength(JSON.stringify(value), 'utf8') >
- config.kv_max_value_size
- ) {
- throw new Error(`value is too large. Max size is ${config.kv_max_value_size}.`);
- }
-
- let app = actor.type?.app ?? undefined;
- const user = actor.type?.user ?? undefined;
- if ( ! user ) throw new Error('User not found');
-
- if ( ! app && app_uid ) {
- app = await get_app({ uid: app_uid });
- }
-
- const db = this.services.get('database').get(DB_WRITE, 'kvstore');
- const key_hash = this.modules.murmurhash.v3(key);
-
- try {
- await db.write(
- `INSERT INTO kv (user_id, app, kkey_hash, kkey, value)
- VALUES (?, ?, ?, ?, ?) ` +
- db.case({
- mysql: 'ON DUPLICATE KEY UPDATE value = ?',
- sqlite: 'ON CONFLICT(user_id, app, kkey_hash) DO UPDATE SET value = excluded.value',
- }),
- [
- user.id, app?.uid ?? 'global', key_hash, key,
- JSON.stringify(value),
- ...db.case({ mysql: [value], otherwise: [] }),
- ]
- );
- } catch (e) {
- // I discovered that my .sqlite file was corrupted and the update
- // above didn't work. The current database initialization does not
- // cause this issue so I'm adding this log as a safeguard.
- // - KernelDeimos / ED
- const svc_error = this.services.get('error-service');
- svc_error.report('kvstore:sqlite_error', {
- message: 'Broken database version - please contact maintainers',
- source: e,
- });
- }
-
- return true;
- },
- del: async function ({ app_uid, key }) {
- const actor = this.context.get('actor');
-
- let app = actor.type?.app ?? undefined;
- const user = actor.type?.user ?? undefined;
- if ( ! user ) throw new Error('User not found');
-
- if ( ! app && app_uid ) {
- app = await get_app({ uid: app_uid });
- }
-
- const db = this.services.get('database').get(DB_WRITE, 'kvstore');
- const key_hash = this.modules.murmurhash.v3(key);
-
- await db.write(
- `DELETE FROM kv WHERE user_id=? AND app=? AND kkey_hash=?`,
- [ user.id, app?.uid ?? 'global', key_hash ]
- );
-
- return true;
- },
- list: async function ({ app_uid, as }) {
- const actor = this.context.get('actor');
-
- let app = actor.type?.app ?? undefined;
- const user = actor.type?.user ?? undefined;
-
- if ( ! app && app_uid ) {
- app = await get_app({ uid: app_uid });
- }
-
- if ( ! user ) throw new Error('User not found');
-
- const db = this.services.get('database').get(DB_READ, 'kvstore');
- let rows = app ? await db.read(
- `SELECT kkey, value FROM kv WHERE user_id=? AND app=?`,
- [ user.id, app.uid ]
- ) : await db.read(
- `SELECT kkey, value FROM kv WHERE user_id=? AND (app IS NULL OR app = 'global')`,
- [ user.id ]
- );
-
- rows = rows.map(row => ({
- key: row.kkey,
- value: db.case({
- mysql: () => row.value,
- otherwise: () => JSON.parse(row.value ?? 'null')
- })(),
- }));
-
- as = as || 'entries';
-
- if ( ! ['keys','values','entries'].includes(as) ) {
- throw APIError.create('field_invalid', null, {
- key: 'as',
- expected: '"keys", "values", or "entries"',
- });
- }
-
- if ( as === 'keys' ) rows = rows.map(row => row.key);
- else if ( as === 'values' ) rows = rows.map(row => row.value);
-
- return rows;
- },
- flush: async function ({ app_uid }) {
- const actor = this.context.get('actor');
-
- let app = actor.type?.app ?? undefined;
- const user = actor.type?.user ?? undefined;
- if ( ! user ) throw new Error('User not found');
-
- if ( ! app && app_uid ) {
- app = await get_app({ uid: app_uid });
- }
-
- const db = this.services.get('database').get(DB_WRITE, 'kvstore');
-
- await db.write(
- `DELETE FROM kv WHERE user_id=? AND app=?`,
- [ user.id, app?.uid ?? 'global' ]
- );
-
- return true;
- }
- }
-}
-
-module.exports = {
- DBKVStore,
-}
diff --git a/src/backend/src/drivers/EntityStoreImplementation.js b/src/backend/src/drivers/EntityStoreImplementation.js
deleted file mode 100644
index a84ae8bd37..0000000000
--- a/src/backend/src/drivers/EntityStoreImplementation.js
+++ /dev/null
@@ -1,171 +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 .
- */
-const APIError = require("../api/APIError");
-const { Driver } = require("../definitions/Driver");
-const { Entity } = require("../om/entitystorage/Entity");
-const { Or, And, Eq } = require("../om/query/query");
-
-const _fetch_based_on_complex_id = async (self, id) => {
- // Ensure `id` is an object and get its keys
- if ( ! id || typeof id !== 'object' || Array.isArray(id) ) {
- throw APIError.create('invalid_id', null, { id });
- }
-
- const id_keys = Object.keys(id);
- // sort keys alphabetically
- id_keys.sort();
-
- // Ensure key set is valid based on redundant keys listing
- const svc_es = self.services.get(self.service);
- const redundant_identifiers = svc_es.om.redundant_identifiers ?? [];
-
- let match_found = false;
- for ( let key of redundant_identifiers ) {
- // Either a single key or a list
- key = Array.isArray(key) ? key : [key];
-
- // All keys in the list must be present in the id
- for ( let i=0 ; i < key.length ; i++ ) {
- if ( ! id_keys.includes(key[i]) ) {
- break;
- }
- if ( i === key.length - 1 ) {
- match_found = true;
- break;
- }
- }
- }
-
- if ( ! match_found ) {
- throw APIError.create('invalid_id', null, { id });
- }
-
- // Construct a query predicate based on the keys
- const key_eqs = [];
- for ( const key of id_keys ) {
- key_eqs.push(new Eq({
- key,
- value: id[key],
- }));
- }
- let predicate = new And({ children: key_eqs });
-
- // Perform a select
- const entity = await svc_es.read({ predicate });
- if ( ! entity ) {
- return null;
- }
-
- // Ensure there is only one result
- return entity;
-}
-
-const _fetch_based_on_either_id = async (self, uid, id) => {
- if ( uid ) {
- const svc_es = self.services.get(self.service);
- return await svc_es.read(uid);
- }
-
- return await _fetch_based_on_complex_id(self, id);
-}
-
-class EntityStoreImplementation extends Driver {
- constructor ({ service }) {
- super();
- this.service = service;
- }
- get_usage_extra () {
- return {
- ['driver.interface']: 'puter-es',
- ['driver.implementation']: 'puter-es:' + this.service,
- };
- }
- static METHODS = {
- create: async function ({ object, options }) {
- const svc_es = this.services.get(this.service);
- if ( object.hasOwnProperty(svc_es.om.primary_identifier) ) {
- throw APIError.create('field_not_allowed_for_create', null, { key: svc_es.om.primary_identifier });
- }
- const entity = await Entity.create({ om: svc_es.om }, object);
- return await svc_es.create(entity, options);
- },
- update: async function ({ object, id, options }) {
- const svc_es = this.services.get(this.service);
- // if ( ! object.hasOwnProperty(svc_es.om.primary_identifier) ) {
- // throw APIError.create('field_required_for_update', null, { key: svc_es.om.primary_identifier });
- // }
- const entity = await Entity.create({ om: svc_es.om }, object);
- return await svc_es.update(entity, id, options);
- },
- upsert: async function ({ object, id, options }) {
- const svc_es = this.services.get(this.service);
- const entity = await Entity.create({ om: svc_es.om }, object);
- return await svc_es.upsert(entity, id, options);
- },
- read: async function ({ uid, id }) {
- if ( ! uid && ! id ) {
- throw APIError.create('xor_field_missing', null, {
- names: ['uid', 'id'],
- });
- }
-
- const entity = await _fetch_based_on_either_id(this, uid, id);
- if ( ! entity ) {
- throw APIError.create('entity_not_found', null, {
- identifier: uid
- });
- }
- return await entity.get_client_safe();
- },
- select: async function (options) {
- const svc_es = this.services.get(this.service);
- const entities = await svc_es.select(options);
- const client_safe_entities = [];
- for ( const entity of entities ) {
- client_safe_entities.push(await entity.get_client_safe());
- }
- return client_safe_entities;
- },
- delete: async function ({ uid, id }) {
- if ( ! uid && ! id ) {
- throw APIError.create('xor_field_missing', null, {
- names: ['uid', 'id'],
- });
- }
-
- if ( id && ! uid ) {
- const entity = await _fetch_based_on_complex_id(this, id);
- if ( ! entity ) {
- throw APIError.create('entity_not_found', null, {
- identifier: id
- });
- }
- const svc_es = this.services.get(this.service);
- uid = await entity.get(svc_es.om.primary_identifier);
- }
-
- const svc_es = this.services.get(this.service);
- return await svc_es.delete(uid);
- },
- };
-}
-
-module.exports = {
- EntityStoreImplementation,
-};
diff --git a/src/backend/src/drivers/HelloWorld.js b/src/backend/src/drivers/HelloWorld.js
deleted file mode 100644
index 058e5d126f..0000000000
--- a/src/backend/src/drivers/HelloWorld.js
+++ /dev/null
@@ -1,43 +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 .
- */
-const { Driver } = require("../definitions/Driver");
-
-class HelloWorld extends Driver {
- static ID = 'public-helloworld';
- static VERSION = '0.0.0';
- static INTERFACE = 'helloworld';
- static SLA = {
- greet: {
- rate_limit: {
- max: 10,
- period: 30000,
- },
- monthly_limit: Math.pow(1, 6),
- },
- }
- static METHODS = {
- greet: async function ({ subject }) {
- return `Hello, ${subject ?? 'World'}!`
- }
- }
-}
-
-module.exports = {
- HelloWorld,
-};
diff --git a/src/backend/src/errors/error_help_details.js b/src/backend/src/errors/error_help_details.js
index 0a37b4861d..8511012a06 100644
--- a/src/backend/src/errors/error_help_details.js
+++ b/src/backend/src/errors/error_help_details.js
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { quot, osclink } = require("../util/strutil");
+const { quot, osclink } = require('@heyputer/putility').libs.string;
const reused = {
runtime_env_references: [
@@ -50,7 +50,7 @@ const error_help_details = [
apply (more) {
more.references = [
...reused.runtime_env_references,
- ]
+ ];
}
},
{
@@ -68,10 +68,10 @@ const error_help_details = [
{
title: 'Set CONFIG_PATH or RUNTIME_PATH environment variable',
},
- ],
+ ];
more.references = [
...reused.runtime_env_references,
- ]
+ ];
}
},
{
@@ -83,7 +83,7 @@ const error_help_details = [
{
title: 'Create a valid config file',
},
- ]
+ ];
}
},
{
@@ -112,7 +112,7 @@ const error_help_details = [
use: 'describes why this error occurs',
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment'
},
- ]
+ ];
}
},
{
@@ -122,7 +122,7 @@ const error_help_details = [
apply (more) {
more.notes = [
'It looks like this might be our fault.',
- ]
+ ];
more.solutions = [
{
title: `Check for an issue on ` +
@@ -135,7 +135,7 @@ const error_help_details = [
'create one'
) + '.'
}
- ]
+ ];
}
},
{
diff --git a/src/backend/src/filesystem/FSNodeContext.js b/src/backend/src/filesystem/FSNodeContext.js
index d575206601..e86b2a9031 100644
--- a/src/backend/src/filesystem/FSNodeContext.js
+++ b/src/backend/src/filesystem/FSNodeContext.js
@@ -18,11 +18,13 @@
*/
const { get_user, get_dir_size, id2path, id2uuid, is_empty, is_shared_with_anyone, suggest_app_for_fsentry, get_app } = require("../helpers");
+const putility = require('@heyputer/putility');
+const { MultiDetachable } = putility.libs.listener;
+const { TDetachable } = putility.traits;
const config = require("../config");
const _path = require('path');
const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require("./node/selectors");
const { Context } = require("../util/context");
-const { MultiDetachable } = require("../util/listenerutil");
const { NodeRawEntrySelector } = require("./node/selectors");
const { DB_READ } = require("../services/database/consts");
const { UserActorType } = require("../services/auth/Actor");
@@ -87,7 +89,7 @@ module.exports = class FSNodeContext {
this.fs = fs;
// Decorate all fetch methods with otel span
- // TODO: language tool for traits; this is a trait
+ // TODO: Apply method decorators using a putility class feature
const fetch_methods = [
'fetchEntry',
'fetchPath',
@@ -271,8 +273,6 @@ module.exports = class FSNodeContext {
resourceService,
} = Context.get('services').values;
- // await this.fs.resourceService
- // .waitForResource(this.selector);
if ( fetch_entry_options.tracer == null ) {
fetch_entry_options.tracer = traceService.tracer;
}
@@ -288,12 +288,8 @@ module.exports = class FSNodeContext {
await new Promise (rslv => {
const detachables = new MultiDetachable();
- let resolved = false;
-
const callback = (resolver) => {
- // NOTE: commented out for now because it's too verbose
- resolved = true;
- detachables.detach();
+ detachables.as(TDetachable).detach();
rslv();
}
@@ -499,8 +495,7 @@ module.exports = class FSNodeContext {
[this.entry.id]
);
const versions_tidy = [];
- for (let index = 0; index < versions.length; index++) {
- const version = versions[index];
+ for ( const version of versions ) {
let username = version.user_id ? (await get_user({id: version.user_id})).username : null;
versions_tidy.push({
id: version.version_id,
@@ -518,13 +513,15 @@ module.exports = class FSNodeContext {
/**
* Fetches the size of a file or directory if it was not
* already fetched.
- * @param {object} user the user is needed to fetch the size
*/
- async fetchSize (user) {
+ async fetchSize () {
const { fsEntryService } = Context.get('services').values;
// we already have the size for files
- if ( ! this.entry.is_dir ) return;
+ if ( ! this.entry.is_dir ) {
+ await this.fetchEntry();
+ return this.entry.size;
+ }
this.entry.size = await fsEntryService.get_recursive_size(
this.entry.uuid,
@@ -551,14 +548,6 @@ module.exports = class FSNodeContext {
this.entry.is_empty = await is_empty(this.uid);
}
- // TODO: this is currently not called anywhere; for now it
- // will never be fetched since sharing is not a priority.
- async fetchIsShared () {
- if ( ! this.mysql_id ) return;
-
- this.entry.is_shared = await is_shared_with_anyone(this.mysql_id);
- }
-
async fetchAll(fsEntryFetcher, user, force) {
await this.fetchEntry({ thumbnail: true });
await this.fetchSubdomains(user);
@@ -610,7 +599,6 @@ module.exports = class FSNodeContext {
);
}
if ( ! this.path ) {
- // console.log('PATH WAS NOT ON ENTRY', this);
await this.fetchPath();
}
if ( ! this.path ) {
diff --git a/src/backend/src/filesystem/FSOperationContext.js b/src/backend/src/filesystem/FSOperationContext.js
deleted file mode 100644
index 53fa0b302c..0000000000
--- a/src/backend/src/filesystem/FSOperationContext.js
+++ /dev/null
@@ -1,324 +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 .
- */
-const PerformanceMonitor = require('../monitor/PerformanceMonitor');
-
-const FSNodeContext = require('./FSNodeContext');
-const FSAccessContext = require('./FSAccessContext');
-const { Context } = require('../util/context');
-
-/**
- * FSOperationContext represents a single operation on the filesystem.
- *
- * FSOperationContext is used to record events such as side-effects
- * which occur during a high-level filesystem operation. It is also
- * responsible for generating a client-safe result which describes
- * the operation.
- */
-module.exports = class FSOperationContext {
- // TODO: rename this.fs to this.access
- constructor (op_name, context, options) {
- // migration: fs:create-service
- // TODO: rename this.fs to this.access
- // NOTE: the 2nd parameter of this constructor
- // was called `fs` and was expected to be FSAccessContext.
- // Now it should be a context object holding the services
- // container. context.access is the FSAccessContext.
- if ( context instanceof FSAccessContext ) {
- this.fs = context;
- } else if ( context ) {
- this.context = context;
- this.fs = context.access;
- } else {
- const x = Context.get();
- this.fs = {};
- this.fs.traceService = x.get('services').get('traceService');
- }
-
- this.name = op_name;
- this.events = [];
- this.parent_dirs_created = [];
- this.created = [];
- this.fields = {};
- this.safeFields = {};
-
- this.valueListeners_ = {};
- this.valueFactories_ = {};
- this.values_ = {};
- this.rejections_ = {};
-
- this.tasks_ = [];
-
- this.currentCheckpoint_ = 'checkpoint not set';
-
- if ( options.parent_operation ) {
- this.parent = options.parent_operation;
- }
-
- this.donePromise = new Promise((resolve, reject) => {
- this.doneResolve = resolve;
- this.doneReject = reject;
- });
-
- // migration: arch:trace-service:move-outta-fs
- if ( this.fs.traceService ) {
- // Set 'span_' to current active span
- const { context, trace } = require('@opentelemetry/api');
- this.span_ = trace.getSpan(context.active());
- }
-
- this.monitor = PerformanceMonitor.createContext(`fs.${op_name}`);
- }
-
- checkpoint (label) {
- this.currentCheckpoint_ = label;
- }
-
- async addTask (name, fn) {
- const task = {
- name,
- operations: [],
- promise: Promise.resolve(),
- };
-
- const taskContext = {
- registerOperation: op => {
- task.operations.push(op);
- task.promise = task.promise.then(() => op.awaitDone());
- }
- };
-
- const monitor = PerformanceMonitor.createContext('fs.rm');
- monitor.label(`task:${name}`);
- task.promise = task.promise.then(() => fn(taskContext));
- this.tasks_.push(task);
-
- let last_promise = null;
- while ( task.promise !== last_promise ) {
- last_promise = task.promise;
- await task.promise;
- }
- // await task.promise;
-
- monitor.stamp();
- monitor.end();
- }
-
- get span () { return this.span_; }
-
- recordParentDirCreated (fsNode) {
- if ( ! fsNode ) {
- throw new Error(
- 'falsy value to recordParentDirCreated',
- fsNode,
- );
- }
- this.parent_dirs_created.push(fsNode);
- }
-
- recordCreated (fsNode) {
- this.created.push(fsNode);
- }
-
- set (field, value) {
- this.fields[field] = value;
- }
-
- async set_now (field, value) {
- this.fields[field] = value;
- if ( value instanceof FSNodeContext ) {
- this.safeFields[field] = await value.getSafeEntry();
- }
- }
-
- get (field) {
- return this.fields[field];
- }
-
- complete (options) {
- options = options ?? {};
-
- if ( this.parent ) {
- for ( const fsNode of this.parent_dirs_created ) {
- this.parent.recordParentDirCreated(fsNode);
- }
-
- for ( const fsNode of this.created ) {
- this.parent.recordCreated(fsNode);
- }
- }
-
- if ( this.tasks_.length > 0 ) {
- // TODO: it's mutating input options, which is not ideal
- if ( ! options.after ) options.after = [];
-
- options.after.push(
- this.tasks_.map(task => task.promise)
- );
- }
-
- if ( options.after ) {
- const thingsToWaitFor = options.after.map(item => {
- if ( item.awaitDone ) return item.awaitDone;
- return item;
- });
- (async () => {
- await Promise.all(thingsToWaitFor);
- this.doneResolve();
- })();
- return;
- }
-
- this.doneResolve();
- }
-
- onComplete(fn) {
- this.donePromise.then(fn);
- }
-
- awaitDone () {
- return this.donePromise;
- }
-
- provideValue (key, value) {
- this.values_[key] = value;
-
- let listeners = this.valueListeners_[key];
- if ( ! listeners ) return;
-
- delete this.valueListeners_[key];
-
- for ( let listener of listeners ) {
- if ( Array.isArray(listener) ) listener = listener[0];
- listener(value);
- }
- }
-
- rejectValue (key, err) {
- this.rejections_[key] = err;
-
- let listeners = this.valueListeners_[key];
- if ( ! listeners ) return;
-
- delete this.valueListeners_[key];
-
- for ( let listener of listeners ) {
- if ( ! Array.isArray(listener) ) continue;
- if ( ! listener[1] ) continue;
- listener = listener[1];
-
- listener(err);
- }
- }
-
- awaitValue (key) {
- return new Promise ((rslv, rjct) => {
- this.onValue(key, rslv, rjct);
- });
- }
-
- onValue (key, fn, rjct) {
- if ( this.values_[key] ) {
- fn(this.values_[key]);
- return;
- }
-
- if ( this.rejections_[key] ) {
- if ( rjct ) {
- rjct(this.rejections_[key]);
- } else throw this.rejections_[key];
- return;
- }
-
- if ( ! this.valueListeners_[key] ) {
- this.valueListeners_[key] = [];
- }
- this.valueListeners_[key].push([fn, rjct]);
-
- if ( this.valueFactories_[key] ) {
- const fn = this.valueFactories_[key];
- delete this.valueFactories_[key];
- (async () => {
- try {
- const value = await fn();
- this.provideValue(key, value);
- } catch (e) {
- this.rejectValue(key, e);
- }
- })();
- }
- }
-
- async setFactory (key, factoryFn) {
- if ( this.valueListeners_[key] ) {
- let v;
- try {
- v = await factoryFn();
- } catch (e) {
- this.rejectValue(key, e);
- }
- this.provideValue(key, v);
- return;
- }
-
- this.valueFactories_[key] = factoryFn;
- }
-
- /**
- * Listen for another operation to complete, and then
- * complete this operation. This is useful for operations
- * which delegate to other operations.
- *
- * @param {FSOperationContext} other
- * @returns {FSOperationContext} this
- */
- completedBy (other) {
- other.onComplete(() => {
- this.complete();
- });
-
- return this;
- }
-
- /**
- * Produces an object which describes the operation in a
- * way that is intended to be sent to the client.
- *
- * @returns {Promise
`;
const index_missing_error = `Please upload an 'index.html' file or if you're uploading a directory, make sure it contains an 'index.html' file at its root.`;
const lock_svg = '';
+const copy_svg = ``;
// authUsername
(async () => {
@@ -518,9 +519,11 @@ function generate_edit_app_section(app) {
-
`;
+ h += ``;
+
// Get window sidebar width
puter.kv.get('window_sidebar_width').then(async (val) => {
let value = parseInt(val);
@@ -1089,7 +1103,7 @@ async function UIDesktop(options){
// User options
// ----------------------------------------------------
let ht = '';
- ht += `
`;
+ ht += `
`;
// logo
ht += ``;
@@ -1098,10 +1112,8 @@ async function UIDesktop(options){
ht += ``;
ht += `
`;
// create account link
@@ -78,16 +76,6 @@ async function UIWindowLogin(options){
h += `
`;
}
h += `
`;
-
- // server and version infomration
- puter.os.version()
- .then(res => {
- const deployed_date = new Date(res.deploy_timestamp).toLocaleString();
- $("#version-placeholder").html(`Version: ${html_encode(res.version)} • Server: ${html_encode(res.location)} • Deployed: ${html_encode(deployed_date)}`);
- })
- .catch(() => {
- $("#version-placeholder").html("Failed to load version or server information.");
- });
const el_window = await UIWindow({
title: null,
@@ -136,6 +124,7 @@ async function UIWindowLogin(options){
UIWindowRecoverPassword({
window_options: {
backdrop: true,
+ stay_on_top: isMobile.phone,
close_on_backdrop_click: false,
}
});
@@ -320,6 +309,10 @@ async function UIWindowLogin(options){
height: 410,
backdrop: true,
is_resizable: false,
+ is_draggable: true,
+ stay_on_top: true,
+ center: true,
+ window_class: 'window-login-2fa',
body_css: {
width: 'initial',
height: '100%',
diff --git a/src/gui/src/UI/UIWindowLoginInProgress.js b/src/gui/src/UI/UIWindowLoginInProgress.js
index ff33fe8e6a..16b2562de2 100644
--- a/src/gui/src/UI/UIWindowLoginInProgress.js
+++ b/src/gui/src/UI/UIWindowLoginInProgress.js
@@ -23,14 +23,26 @@ async function UIWindowLoginInProgress(options){
return new Promise(async (resolve) => {
options = options ?? {};
- let h = '';
+ // get the profile picture of the user
+ let profile_pic
+
+ if(options.user_info?.username){
+ profile_pic = await get_profile_picture(options.user_info?.username);
+ }
+
+ if(!profile_pic){
+ profile_pic = window.icons['profile.svg']
+ }
+
+ let h = '';
h += `
`;
+ h += ``;
h += `
Logging in as ${options.user_info.email === null ? options.user_info.username : options.user_info.email}
`;
+ font-weight: 300; margin: -10px 10px 4px 10px;">Logging in as ${options.user_info.email === null ? options.user_info.username : options.user_info.email}`;
// spinner
- h +=``;
+ h +=``;
h += `
`;
diff --git a/src/gui/src/UI/UIWindowPublishWebsite.js b/src/gui/src/UI/UIWindowPublishWebsite.js
index 619a323e2d..279364a020 100644
--- a/src/gui/src/UI/UIWindowPublishWebsite.js
+++ b/src/gui/src/UI/UIWindowPublishWebsite.js
@@ -100,7 +100,7 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di
});
// find all items whose path starts with target_dir_path
- $(`.item[data-path^="${target_dir_path}"]`).each(function(){
+ $(`.item[data-path^="${target_dir_path}/"]`).each(function(){
// show the link badge
$(this).find('.item-has-website-url-badge').show();
// update item's website_url attribute
diff --git a/src/gui/src/UI/UIWindowRefer.js b/src/gui/src/UI/UIWindowRefer.js
index 2bc3a791b0..0fbddacbce 100644
--- a/src/gui/src/UI/UIWindowRefer.js
+++ b/src/gui/src/UI/UIWindowRefer.js
@@ -37,6 +37,7 @@ async function UIWindowRefer(options){
const el_window = await UIWindow({
title: `Refer a friend!`,
+ window_class: 'window-refer-friend',
icon: null,
uid: null,
is_dir: false,
diff --git a/src/gui/src/UI/UIWindowSearch.js b/src/gui/src/UI/UIWindowSearch.js
index e056f696d3..4d07b786c3 100644
--- a/src/gui/src/UI/UIWindowSearch.js
+++ b/src/gui/src/UI/UIWindowSearch.js
@@ -19,6 +19,9 @@ async function UIWindowSearch(options) {
h += ``;
h += `
`;
h += ``;
+ h += ``;
+ h += ``;
+ h += `
`;
const el_window = await UIWindow({
icon: null,
@@ -38,8 +41,14 @@ async function UIWindowSearch(options) {
allow_native_ctxmenu: true,
allow_user_select: true,
window_class: 'window-search',
+ backdrop: true,
+ center: isMobile.phone,
+ onAppend: function(el_window){
+ },
+
width: 500,
dominant: true,
+
window_css: {
height: 'initial',
padding: '0',
diff --git a/src/gui/src/UI/UIWindowTaskManager.js b/src/gui/src/UI/UIWindowTaskManager.js
index 3716030c4f..4a5d6575a5 100644
--- a/src/gui/src/UI/UIWindowTaskManager.js
+++ b/src/gui/src/UI/UIWindowTaskManager.js
@@ -325,6 +325,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () {
const w = await UIComponentWindow({
component: task_manager_table,
+ window_class: 'window-task-manager',
title: i18n('task_manager'),
icon: globalThis.icons['cog.svg'],
uid: null,
diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css
index 9cc26043a8..4c7d1637e2 100644
--- a/src/gui/src/css/style.css
+++ b/src/gui/src/css/style.css
@@ -351,9 +351,8 @@ input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, sel
}
.device-phone .desktop {
- height: 100vh !important;
- height: 100dvh !important;
- overflow-x: scroll;
+ height: calc(100vh - 90px) !important;
+ height: calc(100dvh - 90px) !important;
}
.item-container-list {
@@ -778,6 +777,10 @@ span.header-sort-icon img {
.device-phone .item-container-list .item .item-name {
line-height: 42px;
+ border-bottom: 1px solid #e3e3e3;
+ padding-bottom: 15px;
+ width: calc(100% - 75px);
+ text-align: left;
}
.window-body .item .item-name-editor {
@@ -919,14 +922,6 @@ span.header-sort-icon img {
top: 0 !important;
}
-.device-phone .window:not(.window-alert), .device-tablet .window:not(.window-alert) {
- border-radius: 0;
- transform: none;
- width: 100%;
- height: 100dvh !important;
- top: 0 !important;
-}
-
.device-phone .window, .device-tablet .window {
z-index: 9999999 !important;
}
@@ -2299,8 +2294,22 @@ label {
display: flex;
justify-content: center;
z-index: 99999;
- box-shadow: 5px 5px 5px 3px #6e6e6e;
overflow: hidden !important;
+
+ height: 50px;
+ border-radius: 10px;
+ bottom: 5px;
+ padding-left: 7px;
+ padding-right: 7px;
+ width: auto;
+ left: 50%;
+ transform: translateX(-50%);
+
+ /* that sweet sweet subtle shadow */
+ box-shadow:
+ inset 0 0 0 0.5px rgba(255, 255, 255, 0.2),
+ 0 0 0 0.5px rgba(0, 0, 0, 0.2),
+ 0 4px 16px rgba(0, 0, 0, 0.2);
}
.taskbar .taskbar-item {
@@ -2367,6 +2376,10 @@ label {
border-radius: 3px;
}
+.device-phone .active-taskbar-indicator{
+ display: none !important;
+}
+
.taskbar .taskbar-icon img {
width: 100%;
height: 100%;
@@ -2382,7 +2395,15 @@ label {
#clock {
display: none;
position: absolute;
- right: 10px;
+ right: 15px;
+ color: white;
+ text-shadow: 0px 0px 3px #00000082, 0px 0px 3px #00000082, 0px 0px 3px #00000082;
+ font-size: 13px;
+ bottom: 5px;
+}
+
+.device-phone #clock {
+ display: none !important;
}
.desktop-bg-settings-wrapper {
@@ -2421,6 +2442,7 @@ label {
overflow: visible !important;
overflow-x: scroll !important;
overflow-y: hidden !important;
+ max-width: calc(100% - 40px);
}
.taskbar .taskbar-item, .taskbar .taskbar-item-sortable-placeholder {
@@ -2429,7 +2451,6 @@ label {
margin-right: 5px;
overflow: visible !important;
padding: 5px 5px 10px 5px;
- position: absolute;
}
.taskbar-icon {
@@ -2589,8 +2610,8 @@ label {
}
.device-phone .popover {
- height: 100vh;
- height: 100dvh;
+ height: calc(100vh - 65px);
+ height: calc(100dvh - 65px);
top: 0 !important;
left: 0 !important;
width: 100%;
@@ -2727,9 +2748,8 @@ label {
.close-launch-popover {
position: absolute;
- top: 5px;
- right: 10px;
- padding: 5px;
+ top: 2px;
+ right: 3px;
display: none;
}
@@ -2785,8 +2805,8 @@ label {
filter: drop-shadow(0px 0px 0.5px rgb(51, 51, 51));
display: block;
margin: 0 auto;
- width: 32px;
- height: 32px;
+ width: 38px;
+ height: 38px;
margin-top: 2px;
}
@@ -3167,7 +3187,6 @@ fieldset[name=number-code] {
}
.login-progress {
- height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
@@ -4413,4 +4432,268 @@ fieldset[name=number-code] {
.search-results .search-result:last-child {
margin-bottom: 0;
-}
\ No newline at end of file
+}
+
+.device-phone .window:not(.window-alert), .device-tablet .window:not(.window-alert) {
+ transform: none;
+ width: 100%;
+}
+
+.device-phone .window.window-explore{
+ border-radius: 0;
+ height: 100dvh !important;
+}
+
+.device-phone .window.window-search{
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ width: calc(100% - 40px);
+ max-width: calc(100% - 40px);
+ max-height: fit-content;
+ border-radius: 5px;
+
+}
+
+.device-phone .window.window-qr, .device-phone .window.window-progress, .device-phone .window.window-login-progress{
+ left: 50% !important;
+ transform: translate(-50%) !important;
+ height: initial !important;
+ max-width: calc(100% - 30px);
+}
+.device-phone .window.window-refer-friend{
+ left: 50% !important;
+ transform: translate(-50%) !important;
+ height: initial !important;
+ max-width: calc(100% - 30px);
+}
+
+.device-phone .window.window-task-manager {
+ height: initial !important;
+}
+
+.device-phone .window.window-feedback{
+ height: initial !important;
+}
+
+.device-phone .window.window-filedialog{
+ transform: none;
+ width: 100% !important;
+ left: 0 !important;
+ min-height: 100dvh;
+ height: 100dvh;
+ top: 0 !important;
+ border-radius: 0 !important;
+}
+
+.device-phone .window.window-app{
+ transform: none;
+ width: 100%;
+ left: 0;
+ height: 100dvh;
+ min-height: 100dvh;
+ top: 0 !important;
+ border-radius: 0;
+}
+
+.device-phone .window.window-login-2fa{
+ left: 50% !important;
+ transform: translate(-50%) !important;
+ height: initial !important;
+ max-width: calc(100% - 30px);
+}
+
+.device-phone .window.window-explorer{
+ transform: none;
+ width: 100%;
+ left: 0;
+ min-height: 100dvh;
+ height: 100dvh;
+ top: 0 !important;
+ border-radius: 0 !important;
+}
+
+.device-phone .window.window-settings{
+ transform: none;
+ width: 100% !important;
+ left: 0 !important;
+ height: 100dvh;
+ top: 0 !important;
+ border-radius: 0;
+}
+
+.device-phone .window-signup{
+ transform: none;
+ width: 100% ;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ border-radius: 0;
+}
+
+.device-phone .send-feedback-btn{
+ width: 100%;
+}
+
+/* Taskbar container */
+.device-phone .taskbar {
+ /* Enable smooth scrolling */
+ -webkit-overflow-scrolling: touch;
+ /* Allow horizontal touch scrolling */
+ touch-action: pan-x;
+ /* Enable horizontal scroll */
+ overflow-x: auto;
+ /* Hide scrollbars while keeping functionality */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ /* Base styling */
+ display: flex;
+ justify-content: left;
+}
+
+/* Hide scrollbar while keeping functionality */
+.device-phone .taskbar::-webkit-scrollbar {
+ display: none;
+}
+
+/* Taskbar items */
+.device-phone .taskbar .taskbar-item {
+ /* Allow dragging while preventing unwanted touch actions */
+ touch-action: pan-x pinch-zoom;
+ /* Ensure items can be dragged */
+ user-select: none;
+ -webkit-user-select: none;
+ cursor: grab;
+
+ /* Base styling */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.device-phone .popover-launcher, .device-phone .launch-popover{
+ border-radius: 0;
+}
+
+/* Main launcher container */
+.device-phone .launch-popover {
+ /* Enable smooth scrolling on iOS */
+ -webkit-overflow-scrolling: touch;
+
+ /* Allow vertical touch scrolling while preventing horizontal */
+ touch-action: pan-y;
+
+ /* Base dimensions */
+ width: 100%;
+ height: 100%;
+
+ /* Scrolling behavior */
+ overflow-y: scroll;
+ overflow-x: hidden;
+
+ /* Background and styling */
+ background-color: rgba(231, 238, 245);
+ padding: 0;
+ margin: 0;
+
+ /* Hide scrollbars while keeping functionality */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+/* Hide scrollbar while keeping functionality */
+.device-phone .launch-popover::-webkit-scrollbar {
+ display: none;
+}
+
+/* Ensure content can receive touch events */
+.device-phone .launch-popover * {
+ touch-action: pan-y;
+}
+
+/* Make sure the search wrapper doesn't interfere with scrolling */
+.launch-search-wrapper {
+ position: sticky;
+ top: -20px;
+ z-index: 1;
+ background: rgba(231, 238, 245);
+ padding-top: 7px;
+}
+.device-phone .launch-search-wrapper {
+ top: 0;
+}
+
+.device-phone .popover-launcher{
+ left: 50% !important;
+ border-radius: 10px;
+ top: 5px !important;
+ transform: translateX(-50%);
+ width: calc(100% - 25px);
+ height: calc(100vh - 65px);
+ height: calc(100dvh - 65px);
+}
+
+.device-phone .start-app-card{
+ width: 25%;
+}
+.device-phone .start-app-icon{
+ width: 50px;
+ height: 50px;
+}
+
+.device-phone .start-app-title{
+ margin-top: 5px;
+}
+
+.device-phone .desktop{
+ position: relative;
+
+ /* Enable smooth scrolling on iOS */
+ -webkit-overflow-scrolling: touch;
+
+ /* Allow vertical touch scrolling while preventing horizontal */
+ touch-action: pan-x;
+
+ /* Hide scrollbars while keeping functionality */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ /* Scrolling behavior */
+ overflow-y: visible !important;
+ overflow-x: scroll;
+
+ padding-bottom: 60px;
+}
+
+.device-phone .desktop * {
+ touch-action: pan-x;
+}
+
+.device-phone .desktop::-webkit-scrollbar {
+ display: none;
+}
+
+.device-phone .window-body.item-container{
+ /* Enable smooth scrolling on iOS */
+ -webkit-overflow-scrolling: touch;
+
+ /* Allow vertical touch scrolling while preventing horizontal */
+ touch-action: pan-y;
+
+ /* Base dimensions */
+ width: 100%;
+ height: 100%;
+
+ /* Scrolling behavior */
+ overflow-y: scroll;
+ overflow-x: hidden;
+
+ /* Hide scrollbars while keeping functionality */
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+.device-phone .window-body.item-container * {
+ touch-action: pan-y;
+}
diff --git a/src/gui/src/globals.js b/src/gui/src/globals.js
index b3b7ea2417..a6c0ad105b 100644
--- a/src/gui/src/globals.js
+++ b/src/gui/src/globals.js
@@ -169,7 +169,7 @@ $( window ).on( "resize", function() {
if(window.is_fullpage_mode) return;
if(window.a_window_is_resizing) return;
- const new_desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height;
+ const new_desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height - 6;
const new_desktop_width = window.innerWidth;
$('.window').each((_, el) => {
diff --git a/src/gui/src/helpers.js b/src/gui/src/helpers.js
index a5b63a1527..f855fa62e6 100644
--- a/src/gui/src/helpers.js
+++ b/src/gui/src/helpers.js
@@ -563,7 +563,6 @@ window.sendWindowWillCloseMsg = function(iframe_element) {
}
window.logout = ()=>{
- console.log('DISP LOGOUT EVENT');
$(document).trigger('logout');
// document.dispatchEvent(new Event("logout", { bubbles: true}));
}
@@ -1624,7 +1623,7 @@ window.move_items = async function(el_items, dest_path, is_undo = false){
// log stats to console
let move_duration = (Date.now() - move_init_ts);
- console.log(`moved ${el_items.length} item${el_items.length > 1 ? 's':''} in ${move_duration}ms`);
+ // console.log(`moved ${el_items.length} item${el_items.length > 1 ? 's':''} in ${move_duration}ms`);
// -----------------------------------------------------------------------
// DONE! close progress window with delay to allow user to see 100% progress
diff --git a/src/gui/src/helpers/launch_app.js b/src/gui/src/helpers/launch_app.js
index 09914e1372..c878cf703d 100644
--- a/src/gui/src/helpers/launch_app.js
+++ b/src/gui/src/helpers/launch_app.js
@@ -77,9 +77,9 @@ const launch_app = async (options)=>{
//-----------------------------------
// maximize on start
//-----------------------------------
- if(app_info.maximize_on_start)
+ if(app_info.maximize_on_start){
options.maximized = 1;
-
+ }
//-----------------------------------
// if opened a file, sign it
//-----------------------------------
diff --git a/src/gui/src/i18n/translations/ar.js b/src/gui/src/i18n/translations/ar.js
index ae3de86376..cc28eff757 100644
--- a/src/gui/src/i18n/translations/ar.js
+++ b/src/gui/src/i18n/translations/ar.js
@@ -20,354 +20,354 @@ const ar = {
english_name: "Arabic",
code: "ar",
dictionary: {
- about: "حول",
- account: "حساب",
- account_password: "تحقق من كلمة مرور الحساب",
- access_granted_to: "تم منح الوصول إلى",
- add_existing_account: "إضافة حساب موجود",
- all_fields_required: "جميع الحقول مطلوبة.",
- allow: "السماح",
- apply: "تطبيق",
- ascending: "تصاعدي",
- associated_websites: "المواقع المرتبطة",
- auto_arrange: "ترتيب تلقائي",
- background: "خلفية",
- browse: "تصفح",
- cancel: "إلغاء",
- center: "مركز",
- change_desktop_background: "تغيير خلفية سطح المكتب...",
- change_email: "تغيير البريد الإلكتروني",
- change_language: "تغيير اللغة",
- change_password: "تغيير كلمة المرور",
- change_ui_colors: "تغيير ألوان واجهة المستخدم",
- change_username: "تغيير اسم المستخدم",
- close: "إغلاق",
- close_all_windows: "إغلاق جميع النوافذ",
- close_all_windows_confirm: "هل أنت متأكد أنك تريد إغلاق جميع النوافذ؟",
- close_all_windows_and_log_out: "إغلاق النوافذ وتسجيل الخروج",
- change_always_open_with: "هل تريد دائمًا فتح هذا النوع من الملفات باستخدام",
- color: "لون",
- confirm: "تأكيد",
- confirm_2fa_setup: "لقد أضفت الرمز إلى تطبيق المصادقة",
- confirm_2fa_recovery: "لقد حفظت رموز الاسترداد في مكان آمن",
- confirm_account_for_free_referral_storage_c2a:
- "أنشئ حسابًا وقم بتأكيد عنوان بريدك الإلكتروني للحصول على 1 جيجابايت من مساحة التخزين المجانية. سيحصل صديقك أيضًا على 1 جيجابايت من مساحة التخزين المجانية.",
- confirm_code_generic_incorrect: "رمز غير صحيح.",
- confirm_code_generic_too_many_requests:
- "طلبات كثيرة جدًا. يرجى الانتظار بضع دقائق.",
- confirm_code_generic_submit: "إرسال الرمز",
- confirm_code_generic_try_again: "حاول مرة أخرى",
- confirm_code_generic_title: "أدخل رمز التأكيد",
- confirm_code_2fa_instruction:
- "أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة الخاص بك.",
- confirm_code_2fa_submit_btn: "إرسال",
- confirm_code_2fa_title: "أدخل رمز المصادقة الثنائية",
- confirm_delete_multiple_items:
- "هل أنت متأكد أنك تريد حذف هذه العناصر نهائيًا؟",
- confirm_delete_single_item: "هل تريد حذف هذا العنصر نهائيًا؟",
- confirm_open_apps_log_out:
- "لديك تطبيقات مفتوحة. هل أنت متأكد أنك تريد تسجيل الخروج؟",
- confirm_new_password: "تأكيد كلمة المرور الجديدة",
- confirm_delete_user:
- "هل أنت متأكد أنك تريد حذف حسابك؟ سيتم حذف جميع ملفاتك وبياناتك نهائيًا. لا يمكن التراجع عن هذا الإجراء.",
- confirm_delete_user_title: "حذف الحساب؟",
- confirm_session_revoke: "هل أنت متأكد أنك تريد إلغاء هذه الجلسة؟",
- confirm_your_email_address: "تأكيد عنوان بريدك الإلكتروني",
- contact_us: "اتصل بنا",
- contact_us_verification_required:
- "يجب أن يكون لديك عنوان بريد إلكتروني مُؤكد لاستخدام هذه الخدمة.",
- contain: "احتواء",
- continue: "استمر",
- copy: "نسخ",
- copy_link: "نسخ الرابط",
- copying: "جارٍ النسخ",
- copying_file: "جارٍ نسخ %%",
- cover: "تغطية",
- create_account: "إنشاء حساب",
- create_free_account: "إنشاء حساب مجاني",
- create_shortcut: "إنشاء اختصار",
- credits: "الاعتمادات",
- current_password: "كلمة المرور الحالية",
- cut: "قص",
- clock: "ساعة",
- clock_visible_hide: "إخفاء - مخفية دائمًا",
- clock_visible_show: "إظهار - مرئية دائمًا",
- clock_visible_auto: "تلقائي - الافتراضي، مرئي فقط في وضع الشاشة الكاملة.",
- close_all: "إغلاق الكل",
- created: "تم الإنشاء",
- date_modified: "تاريخ التعديل",
- default: "افتراضي",
- delete: "حذف",
- delete_account: "حذف الحساب",
- delete_permanently: "حذف نهائي",
- deleting_file: "جارٍ حذف %%",
- deploy_as_app: "نشر كتطبيق",
- descending: "تنازلي",
- desktop: "سطح المكتب",
- desktop_background_fit: "ملائمة",
- developers: "المطورين",
- dir_published_as_website: "%strong% تم نشره إلى:",
- disable_2fa: "تعطيل المصادقة الثنائية",
- disable_2fa_confirm: "هل أنت متأكد أنك تريد تعطيل المصادقة الثنائية؟",
- disable_2fa_instructions: "أدخل كلمة المرور لتعطيل المصادقة الثنائية.",
- disassociate_dir: "فصل الدليل",
- documents: "المستندات",
- dont_allow: "عدم السماح",
- download: "تنزيل",
- download_file: "تنزيل الملف",
- downloading: "جارٍ التنزيل",
- email: "البريد الإلكتروني",
- email_change_confirmation_sent:
- "تم إرسال بريد تأكيد إلى عنوان بريدك الإلكتروني الجديد. يرجى التحقق من صندوق الوارد واتباع التعليمات لإكمال العملية.",
- email_invalid: "البريد الإلكتروني غير صالح.",
- email_or_username: "البريد الإلكتروني أو اسم المستخدم",
- email_required: "البريد الإلكتروني مطلوب.",
- empty_trash: "إفراغ سلة المهملات",
- empty_trash_confirmation:
- "هل أنت متأكد أنك تريد حذف العناصر في سلة المهملات نهائيًا؟",
- emptying_trash: "جارٍ إفراغ سلة المهملات...",
- enable_2fa: "تمكين المصادقة الثنائية",
- end_hard: "إنهاء صعب",
- end_process_force_confirm:
- "هل أنت متأكد أنك تريد إنهاء هذه العملية بالقوة؟",
- end_soft: "إنهاء سلس",
- enlarged_qr_code: "رمز QR مكبر",
- enter_password_to_confirm_delete_user: "أدخل كلمة المرور لتأكيد حذف الحساب",
- error_message_is_missing: "رسالة الخطأ مفقودة.",
- error_unknown_cause: "حدث خطأ غير معروف.",
- error_uploading_files: "فشل في تحميل الملفات",
- favorites: "المفضلة",
- feedback: "ملاحظات",
- feedback_c2a:
- "يرجى استخدام النموذج أدناه لإرسال ملاحظاتك وتعليقاتك وتقرير الأخطاء.",
- feedback_sent_confirmation:
- "شكرًا لتواصلك معنا. إذا كان لديك بريد إلكتروني مرتبط بحسابك، ستتلقى ردًا منا في أقرب وقت ممكن.",
- fit: "ملائمة",
- folder: "مجلد",
- force_quit: "إنهاء بالقوة",
- forgot_pass_c2a: "هل نسيت كلمة المرور؟",
- from: "من",
- general: "عام",
- get_a_copy_of_on_puter: "احصل على نسخة من '%%' على Puter.com!",
- get_copy_link: "احصل على رابط النسخ",
- hide_all_windows: "إخفاء جميع النوافذ",
- home: "الصفحة الرئيسية",
- html_document: "مستند HTML",
- hue: "درجة اللون",
- image: "صورة",
- incorrect_password: "كلمة مرور غير صحيحة",
- invite_link: "رابط الدعوة",
- item: "عنصر",
- items_in_trash_cannot_be_renamed:
- "لا يمكن إعادة تسمية هذا العنصر لأنه في سلة المهملات. لإعادة تسمية هذا العنصر، اسحبه أولاً خارج سلة المهملات.",
- jpeg_image: "صورة JPEG",
- keep_in_taskbar: "الاحتفاظ في شريط المهام",
- language: "اللغة",
- license: "رخصة",
- lightness: "إضاءة",
- link_copied: "تم نسخ الرابط",
- loading: "جارٍ التحميل",
- log_in: "تسجيل الدخول",
- log_into_another_account_anyway: "تسجيل الدخول إلى حساب آخر على أي حال",
- log_out: "تسجيل الخروج",
- looks_good: "يبدو جيدًا!",
- manage_sessions: "إدارة الجلسات",
- menubar_style: "نمط شريط القوائم",
- menubar_style_desktop: "سطح المكتب",
- menubar_style_system: "النظام",
- menubar_style_window: "النافذة",
- modified: "تم التعديل",
- move: "نقل",
- moving_file: "جارٍ نقل %%",
- my_websites: "مواقعي الإلكترونية",
- name: "اسم",
- name_cannot_be_empty: "الاسم لا يمكن أن يكون فارغًا.",
- name_cannot_contain_double_period: "الاسم لا يمكن أن يكون '..'.",
- name_cannot_contain_period: "الاسم لا يمكن أن يكون '.'.",
- name_cannot_contain_slash: "الاسم لا يمكن أن يحتوي على '/'.",
- name_must_be_string: "الاسم يجب أن يكون نصًا فقط.",
- name_too_long: "الاسم لا يمكن أن يكون أطول من %% حرف.",
- new: "جديد",
- new_email: "البريد الإلكتروني الجديد",
- new_folder: "مجلد جديد",
- new_password: "كلمة المرور الجديدة",
- new_username: "اسم المستخدم الجديد",
- no: "لا",
- no_dir_associated_with_site: "لا يوجد دليل مرتبط بهذا العنوان.",
- no_websites_published: "لم تنشر أي مواقع إلكترونية بعد.",
- ok: "موافق",
- open: "فتح",
- open_in_new_tab: "فتح في علامة تبويب جديدة",
- open_in_new_window: "فتح في نافذة جديدة",
- open_with: "فتح باستخدام",
- original_name: "الاسم الأصلي",
- original_path: "المسار الأصلي",
- oss_code_and_content: "برامج ومحتوى مفتوح المصدر",
- password: "كلمة المرور",
- password_changed: "تم تغيير كلمة المرور.",
- password_recovery_rate_limit:
- "لقد وصلت إلى الحد الأقصى؛ يرجى الانتظار بضع دقائق. لمنع حدوث ذلك في المستقبل، تجنب إعادة تحميل الصفحة كثيرًا.",
- password_recovery_token_invalid: "رمز استعادة كلمة المرور لم يعد صالحًا.",
- password_recovery_unknown_error:
- "حدث خطأ غير معروف. يرجى المحاولة مرة أخرى لاحقًا.",
- password_required: "كلمة المرور مطلوبة.",
- password_strength_error:
- "يجب أن تكون كلمة المرور بطول 8 أحرف على الأقل وتحتوي على حرف كبير واحد، حرف صغير واحد، رقم واحد، وحرف خاص واحد على الأقل.",
- passwords_do_not_match:
- "`كلمة المرور الجديدة` و`تأكيد كلمة المرور الجديدة` غير متطابقتين.",
- paste: "لصق",
- paste_into_folder: "لصق في المجلد",
- path: "المسار",
- personalization: "تخصيص",
- pick_name_for_website: "اختر اسمًا لموقعك الإلكتروني:",
- picture: "صورة",
- pictures: "الصور",
- plural_suffix: "s",
- powered_by_puter_js: "مدعوم بواسطة {{link=docs}}Puter.js{{/link}}",
- preparing: "جارٍ التحضير...",
- preparing_for_upload: "جارٍ التحضير للتحميل...",
- print: "طباعة",
- privacy: "الخصوصية",
- proceed_to_login: "المتابعة لتسجيل الدخول",
- proceed_with_account_deletion: "المتابعة مع حذف الحساب",
- process_status_initializing: "جارٍ التهيئة",
- process_status_running: "جارٍ التشغيل",
- process_type_app: "تطبيق",
- process_type_init: "تهيئة",
- process_type_ui: "واجهة المستخدم",
- properties: "الخصائص",
- public: "عام",
- publish: "نشر",
- publish_as_website: "نشر كموقع إلكتروني",
- puter_description:
- "Puter هو سحابة شخصية تركز على الخصوصية للحفاظ على جميع ملفاتك، تطبيقاتك، وألعابك في مكان آمن واحد، متاحة من أي مكان وفي أي وقت.",
- reading_file: "جارٍ قراءة %strong%",
- recent: "الأخيرة",
- recommended: "مُوصى به",
- recover_password: "استعادة كلمة المرور",
- refer_friends_c2a:
- "احصل على 1 جيجابايت عن كل صديق ينشئ حسابًا ويؤكده على Puter. سيحصل صديقك على 1 جيجابايت أيضًا!",
- refer_friends_social_media_c2a:
- "احصل على 1 جيجابايت من التخزين المجاني على Puter.com!",
- refresh: "تحديث",
- release_address_confirmation: "هل أنت متأكد أنك تريد تحرير هذا العنوان؟",
- remove_from_taskbar: "إزالة من شريط المهام",
- rename: "إعادة تسمية",
- repeat: "تكرار",
- replace: "استبدال",
- replace_all: "استبدال الكل",
- resend_confirmation_code: "إعادة إرسال رمز التأكيد",
- reset_colors: "إعادة ضبط الألوان",
- restart_puter_confirm: "هل أنت متأكد أنك تريد إعادة تشغيل Puter؟",
- restore: "استعادة",
- save: "حفظ",
- saturation: "تشبع",
- save_account: "حفظ الحساب",
- save_account_to_get_copy_link: "يرجى إنشاء حساب للمتابعة.",
- save_account_to_publish: "يرجى إنشاء حساب للمتابعة.",
- save_session: "حفظ الجلسة",
- save_session_c2a: "أنشئ حسابًا لحفظ جلستك الحالية وتجنب فقدان عملك.",
- scan_qr_c2a: "امسح الرمز أدناه لتسجيل الدخول إلى هذه الجلسة من أجهزة أخرى",
- scan_qr_2fa: "امسح رمز الاستجابة السريعة باستخدام تطبيق المصادقة الخاص بك",
- scan_qr_generic:
- "امسح رمز الاستجابة السريعة هذا باستخدام هاتفك أو جهاز آخر",
- search: "بحث",
- seconds: "ثوانٍ",
- security: "الأمان",
- select: "تحديد",
- selected: "محدد",
- select_color: "اختر لونًا…",
- sessions: "جلسات",
- send: "إرسال",
- send_password_recovery_email: "إرسال بريد استعادة كلمة المرور",
- session_saved: "شكرًا لإنشاء حساب. تم حفظ هذه الجلسة.",
- settings: "الإعدادات",
- set_new_password: "تعيين كلمة مرور جديدة",
- share: "مشاركة",
- share_to: "مشاركة إلى",
- share_with: "مشاركة مع:",
- shortcut_to: "اختصار إلى",
- show_all_windows: "عرض جميع النوافذ",
- show_hidden: "إظهار المخفي",
- sign_in_with_puter: "تسجيل الدخول باستخدام Puter",
- sign_up: "تسجيل",
- signing_in: "جارٍ تسجيل الدخول…",
- size: "الحجم",
- skip: "تخطي",
- something_went_wrong: "حدث خطأ ما.",
- sort_by: "فرز حسب",
- start: "بدء",
- status: "الحالة",
- storage_usage: "استخدام التخزين",
- storage_puter_used: "مستخدم بواسطة Puter",
- taking_longer_than_usual: "يستغرق وقتًا أطول من المعتاد. يرجى الانتظار...",
- task_manager: "مدير المهام",
- taskmgr_header_name: "الاسم",
- taskmgr_header_status: "الحالة",
- taskmgr_header_type: "النوع",
- terms: "الشروط",
- text_document: "مستند نصي",
- tos_fineprint:
- "بالنقر على 'إنشاء حساب مجاني' فإنك توافق على {{link=terms}}شروط الخدمة{{/link}} و{{link=privacy}}سياسة الخصوصية{{/link}} لـPuter.",
- transparency: "الشفافية",
- trash: "المهملات",
- two_factor: "المصادقة الثنائية",
- two_factor_disabled: "تم تعطيل المصادقة الثنائية",
- two_factor_enabled: "تم تمكين المصادقة الثنائية",
- type: "نوع",
- type_confirm_to_delete_account: "اكتب 'تأكيد' لحذف حسابك.",
- ui_colors: "ألوان واجهة المستخدم",
- ui_manage_sessions: "مدير الجلسات",
- ui_revoke: "إلغاء",
- undo: "تراجع",
- unlimited: "غير محدود",
- unzip: "فك الضغط",
- upload: "رفع",
- upload_here: "ارفع هنا",
- usage: "الاستخدام",
- username: "اسم المستخدم",
- username_changed: "تم تحديث اسم المستخدم بنجاح.",
- username_required: "اسم المستخدم مطلوب.",
- versions: "الإصدارات",
- videos: "مقاطع الفيديو",
- visibility: "الرؤية",
- yes: "نعم",
- yes_release_it: "نعم، أطلقه",
- you_have_been_referred_to_puter_by_a_friend:
- "تم إحالتك إلى Puter بواسطة صديق!",
- zip: "ضغط",
- zipping_file: "جارٍ ضغط %strong%",
+ about: "حول",
+ account: "حساب",
+ account_password: "تحقق من كلمة مرور الحساب",
+ access_granted_to: "تم منح الوصول إلى",
+ add_existing_account: "إضافة حساب موجود",
+ all_fields_required: "جميع الحقول مطلوبة.",
+ allow: "السماح",
+ apply: "تطبيق",
+ ascending: "تصاعدي",
+ associated_websites: "المواقع المرتبطة",
+ auto_arrange: "ترتيب تلقائي",
+ background: "خلفية",
+ browse: "تصفح",
+ cancel: "إلغاء",
+ center: "مركز",
+ change_desktop_background: "تغيير خلفية سطح المكتب...",
+ change_email: "تغيير البريد الإلكتروني",
+ change_language: "تغيير اللغة",
+ change_password: "تغيير كلمة المرور",
+ change_ui_colors: "تغيير ألوان واجهة المستخدم",
+ change_username: "تغيير اسم المستخدم",
+ close: "إغلاق",
+ close_all_windows: "إغلاق جميع النوافذ",
+ close_all_windows_confirm: "هل أنت متأكد أنك تريد إغلاق جميع النوافذ؟",
+ close_all_windows_and_log_out: "إغلاق النوافذ وتسجيل الخروج",
+ change_always_open_with: "هل تريد دائمًا فتح هذا النوع من الملفات باستخدام",
+ color: "لون",
+ confirm: "تأكيد",
+ confirm_2fa_setup: "لقد أضفت الرمز إلى تطبيق المصادقة",
+ confirm_2fa_recovery: "لقد حفظت رموز الاسترداد في مكان آمن",
+ confirm_account_for_free_referral_storage_c2a:
+ "أنشئ حسابًا وقم بتأكيد عنوان بريدك الإلكتروني للحصول على 1 جيجابايت من مساحة التخزين المجانية. سيحصل صديقك أيضًا على 1 جيجابايت من مساحة التخزين المجانية.",
+ confirm_code_generic_incorrect: "رمز غير صحيح.",
+ confirm_code_generic_too_many_requests:
+ "طلبات كثيرة جدًا. يرجى الانتظار بضع دقائق.",
+ confirm_code_generic_submit: "إرسال الرمز",
+ confirm_code_generic_try_again: "حاول مرة أخرى",
+ confirm_code_generic_title: "أدخل رمز التأكيد",
+ confirm_code_2fa_instruction:
+ "أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة الخاص بك.",
+ confirm_code_2fa_submit_btn: "إرسال",
+ confirm_code_2fa_title: "أدخل رمز المصادقة الثنائية",
+ confirm_delete_multiple_items:
+ "هل أنت متأكد أنك تريد حذف هذه العناصر نهائيًا؟",
+ confirm_delete_single_item: "هل تريد حذف هذا العنصر نهائيًا؟",
+ confirm_open_apps_log_out:
+ "لديك تطبيقات مفتوحة. هل أنت متأكد أنك تريد تسجيل الخروج؟",
+ confirm_new_password: "تأكيد كلمة المرور الجديدة",
+ confirm_delete_user:
+ "هل أنت متأكد أنك تريد حذف حسابك؟ سيتم حذف جميع ملفاتك وبياناتك نهائيًا. لا يمكن التراجع عن هذا الإجراء.",
+ confirm_delete_user_title: "حذف الحساب؟",
+ confirm_session_revoke: "هل أنت متأكد أنك تريد إلغاء هذه الجلسة؟",
+ confirm_your_email_address: "تأكيد عنوان بريدك الإلكتروني",
+ contact_us: "اتصل بنا",
+ contact_us_verification_required:
+ "يجب أن يكون لديك عنوان بريد إلكتروني مُؤكد لاستخدام هذه الخدمة.",
+ contain: "احتواء",
+ continue: "استمر",
+ copy: "نسخ",
+ copy_link: "نسخ الرابط",
+ copying: "جارٍ النسخ",
+ copying_file: "جارٍ نسخ %%",
+ cover: "تغطية",
+ create_account: "إنشاء حساب",
+ create_free_account: "إنشاء حساب مجاني",
+ create_shortcut: "إنشاء اختصار",
+ credits: "الاعتمادات",
+ current_password: "كلمة المرور الحالية",
+ cut: "قص",
+ clock: "ساعة",
+ clock_visible_hide: "إخفاء - مخفية دائمًا",
+ clock_visible_show: "إظهار - مرئية دائمًا",
+ clock_visible_auto: "تلقائي - الافتراضي، مرئي فقط في وضع الشاشة الكاملة.",
+ close_all: "إغلاق الكل",
+ created: "تم الإنشاء",
+ date_modified: "تاريخ التعديل",
+ default: "افتراضي",
+ delete: "حذف",
+ delete_account: "حذف الحساب",
+ delete_permanently: "حذف نهائي",
+ deleting_file: "جارٍ حذف %%",
+ deploy_as_app: "نشر كتطبيق",
+ descending: "تنازلي",
+ desktop: "سطح المكتب",
+ desktop_background_fit: "ملائمة",
+ developers: "المطورين",
+ dir_published_as_website: "%strong% تم نشره إلى:",
+ disable_2fa: "تعطيل المصادقة الثنائية",
+ disable_2fa_confirm: "هل أنت متأكد أنك تريد تعطيل المصادقة الثنائية؟",
+ disable_2fa_instructions: "أدخل كلمة المرور لتعطيل المصادقة الثنائية.",
+ disassociate_dir: "فصل الدليل",
+ documents: "المستندات",
+ dont_allow: "عدم السماح",
+ download: "تنزيل",
+ download_file: "تنزيل الملف",
+ downloading: "جارٍ التنزيل",
+ email: "البريد الإلكتروني",
+ email_change_confirmation_sent:
+ "تم إرسال بريد تأكيد إلى عنوان بريدك الإلكتروني الجديد. يرجى التحقق من صندوق الوارد واتباع التعليمات لإكمال العملية.",
+ email_invalid: "البريد الإلكتروني غير صالح.",
+ email_or_username: "البريد الإلكتروني أو اسم المستخدم",
+ email_required: "البريد الإلكتروني مطلوب.",
+ empty_trash: "إفراغ سلة المهملات",
+ empty_trash_confirmation:
+ "هل أنت متأكد أنك تريد حذف العناصر في سلة المهملات نهائيًا؟",
+ emptying_trash: "جارٍ إفراغ سلة المهملات...",
+ enable_2fa: "تمكين المصادقة الثنائية",
+ end_hard: "إنهاء صعب",
+ end_process_force_confirm:
+ "هل أنت متأكد أنك تريد إنهاء هذه العملية بالقوة؟",
+ end_soft: "إنهاء سلس",
+ enlarged_qr_code: "رمز QR مكبر",
+ enter_password_to_confirm_delete_user: "أدخل كلمة المرور لتأكيد حذف الحساب",
+ error_message_is_missing: "رسالة الخطأ مفقودة.",
+ error_unknown_cause: "حدث خطأ غير معروف.",
+ error_uploading_files: "فشل في تحميل الملفات",
+ favorites: "المفضلة",
+ feedback: "ملاحظات",
+ feedback_c2a:
+ "يرجى استخدام النموذج أدناه لإرسال ملاحظاتك وتعليقاتك وتقرير الأخطاء.",
+ feedback_sent_confirmation:
+ "شكرًا لتواصلك معنا. إذا كان لديك بريد إلكتروني مرتبط بحسابك، ستتلقى ردًا منا في أقرب وقت ممكن.",
+ fit: "ملائمة",
+ folder: "مجلد",
+ force_quit: "إنهاء بالقوة",
+ forgot_pass_c2a: "هل نسيت كلمة المرور؟",
+ from: "من",
+ general: "عام",
+ get_a_copy_of_on_puter: "احصل على نسخة من '%%' على Puter.com!",
+ get_copy_link: "احصل على رابط النسخ",
+ hide_all_windows: "إخفاء جميع النوافذ",
+ home: "الصفحة الرئيسية",
+ html_document: "مستند HTML",
+ hue: "درجة اللون",
+ image: "صورة",
+ incorrect_password: "كلمة مرور غير صحيحة",
+ invite_link: "رابط الدعوة",
+ item: "عنصر",
+ items_in_trash_cannot_be_renamed:
+ "لا يمكن إعادة تسمية هذا العنصر لأنه في سلة المهملات. لإعادة تسمية هذا العنصر، اسحبه أولاً خارج سلة المهملات.",
+ jpeg_image: "صورة JPEG",
+ keep_in_taskbar: "الاحتفاظ في شريط المهام",
+ language: "اللغة",
+ license: "رخصة",
+ lightness: "إضاءة",
+ link_copied: "تم نسخ الرابط",
+ loading: "جارٍ التحميل",
+ log_in: "تسجيل الدخول",
+ log_into_another_account_anyway: "تسجيل الدخول إلى حساب آخر على أي حال",
+ log_out: "تسجيل الخروج",
+ looks_good: "يبدو جيدًا!",
+ manage_sessions: "إدارة الجلسات",
+ menubar_style: "نمط شريط القوائم",
+ menubar_style_desktop: "سطح المكتب",
+ menubar_style_system: "النظام",
+ menubar_style_window: "النافذة",
+ modified: "تم التعديل",
+ move: "نقل",
+ moving_file: "جارٍ نقل %%",
+ my_websites: "مواقعي الإلكترونية",
+ name: "اسم",
+ name_cannot_be_empty: "الاسم لا يمكن أن يكون فارغًا.",
+ name_cannot_contain_double_period: "الاسم لا يمكن أن يكون '..'.",
+ name_cannot_contain_period: "الاسم لا يمكن أن يكون '.'.",
+ name_cannot_contain_slash: "الاسم لا يمكن أن يحتوي على '/'.",
+ name_must_be_string: "الاسم يجب أن يكون نصًا فقط.",
+ name_too_long: "الاسم لا يمكن أن يكون أطول من %% حرف.",
+ new: "جديد",
+ new_email: "البريد الإلكتروني الجديد",
+ new_folder: "مجلد جديد",
+ new_password: "كلمة المرور الجديدة",
+ new_username: "اسم المستخدم الجديد",
+ no: "لا",
+ no_dir_associated_with_site: "لا يوجد دليل مرتبط بهذا العنوان.",
+ no_websites_published: "لم تنشر أي مواقع إلكترونية بعد.",
+ ok: "موافق",
+ open: "فتح",
+ open_in_new_tab: "فتح في علامة تبويب جديدة",
+ open_in_new_window: "فتح في نافذة جديدة",
+ open_with: "فتح باستخدام",
+ original_name: "الاسم الأصلي",
+ original_path: "المسار الأصلي",
+ oss_code_and_content: "برامج ومحتوى مفتوح المصدر",
+ password: "كلمة المرور",
+ password_changed: "تم تغيير كلمة المرور.",
+ password_recovery_rate_limit:
+ "لقد وصلت إلى الحد الأقصى؛ يرجى الانتظار بضع دقائق. لمنع حدوث ذلك في المستقبل، تجنب إعادة تحميل الصفحة كثيرًا.",
+ password_recovery_token_invalid: "رمز استعادة كلمة المرور لم يعد صالحًا.",
+ password_recovery_unknown_error:
+ "حدث خطأ غير معروف. يرجى المحاولة مرة أخرى لاحقًا.",
+ password_required: "كلمة المرور مطلوبة.",
+ password_strength_error:
+ "يجب أن تكون كلمة المرور بطول 8 أحرف على الأقل وتحتوي على حرف كبير واحد، حرف صغير واحد، رقم واحد، وحرف خاص واحد على الأقل.",
+ passwords_do_not_match:
+ "`كلمة المرور الجديدة` و`تأكيد كلمة المرور الجديدة` غير متطابقتين.",
+ paste: "لصق",
+ paste_into_folder: "لصق في المجلد",
+ path: "المسار",
+ personalization: "تخصيص",
+ pick_name_for_website: "اختر اسمًا لموقعك الإلكتروني:",
+ picture: "صورة",
+ pictures: "الصور",
+ plural_suffix: "s",
+ powered_by_puter_js: "مدعوم بواسطة {{link=docs}}Puter.js{{/link}}",
+ preparing: "جارٍ التحضير...",
+ preparing_for_upload: "جارٍ التحضير للتحميل...",
+ print: "طباعة",
+ privacy: "الخصوصية",
+ proceed_to_login: "المتابعة لتسجيل الدخول",
+ proceed_with_account_deletion: "المتابعة مع حذف الحساب",
+ process_status_initializing: "جارٍ التهيئة",
+ process_status_running: "جارٍ التشغيل",
+ process_type_app: "تطبيق",
+ process_type_init: "تهيئة",
+ process_type_ui: "واجهة المستخدم",
+ properties: "الخصائص",
+ public: "عام",
+ publish: "نشر",
+ publish_as_website: "نشر كموقع إلكتروني",
+ puter_description:
+ "Puter هو سحابة شخصية تركز على الخصوصية للحفاظ على جميع ملفاتك، تطبيقاتك، وألعابك في مكان آمن واحد، متاحة من أي مكان وفي أي وقت.",
+ reading_file: "جارٍ قراءة %strong%",
+ recent: "الأخيرة",
+ recommended: "مُوصى به",
+ recover_password: "استعادة كلمة المرور",
+ refer_friends_c2a:
+ "احصل على 1 جيجابايت عن كل صديق ينشئ حسابًا ويؤكده على Puter. سيحصل صديقك على 1 جيجابايت أيضًا!",
+ refer_friends_social_media_c2a:
+ "احصل على 1 جيجابايت من التخزين المجاني على Puter.com!",
+ refresh: "تحديث",
+ release_address_confirmation: "هل أنت متأكد أنك تريد تحرير هذا العنوان؟",
+ remove_from_taskbar: "إزالة من شريط المهام",
+ rename: "إعادة تسمية",
+ repeat: "تكرار",
+ replace: "استبدال",
+ replace_all: "استبدال الكل",
+ resend_confirmation_code: "إعادة إرسال رمز التأكيد",
+ reset_colors: "إعادة ضبط الألوان",
+ restart_puter_confirm: "هل أنت متأكد أنك تريد إعادة تشغيل Puter؟",
+ restore: "استعادة",
+ save: "حفظ",
+ saturation: "تشبع",
+ save_account: "حفظ الحساب",
+ save_account_to_get_copy_link: "يرجى إنشاء حساب للمتابعة.",
+ save_account_to_publish: "يرجى إنشاء حساب للمتابعة.",
+ save_session: "حفظ الجلسة",
+ save_session_c2a: "أنشئ حسابًا لحفظ جلستك الحالية وتجنب فقدان عملك.",
+ scan_qr_c2a: "امسح الرمز أدناه لتسجيل الدخول إلى هذه الجلسة من أجهزة أخرى",
+ scan_qr_2fa: "امسح رمز الاستجابة السريعة باستخدام تطبيق المصادقة الخاص بك",
+ scan_qr_generic:
+ "امسح رمز الاستجابة السريعة هذا باستخدام هاتفك أو جهاز آخر",
+ search: "بحث",
+ seconds: "ثوانٍ",
+ security: "الأمان",
+ select: "تحديد",
+ selected: "محدد",
+ select_color: "اختر لونًا…",
+ sessions: "جلسات",
+ send: "إرسال",
+ send_password_recovery_email: "إرسال بريد استعادة كلمة المرور",
+ session_saved: "شكرًا لإنشاء حساب. تم حفظ هذه الجلسة.",
+ settings: "الإعدادات",
+ set_new_password: "تعيين كلمة مرور جديدة",
+ share: "مشاركة",
+ share_to: "مشاركة إلى",
+ share_with: "مشاركة مع:",
+ shortcut_to: "اختصار إلى",
+ show_all_windows: "عرض جميع النوافذ",
+ show_hidden: "إظهار المخفي",
+ sign_in_with_puter: "تسجيل الدخول باستخدام Puter",
+ sign_up: "تسجيل",
+ signing_in: "جارٍ تسجيل الدخول…",
+ size: "الحجم",
+ skip: "تخطي",
+ something_went_wrong: "حدث خطأ ما.",
+ sort_by: "فرز حسب",
+ start: "بدء",
+ status: "الحالة",
+ storage_usage: "استخدام التخزين",
+ storage_puter_used: "مستخدم بواسطة Puter",
+ taking_longer_than_usual: "يستغرق وقتًا أطول من المعتاد. يرجى الانتظار...",
+ task_manager: "مدير المهام",
+ taskmgr_header_name: "الاسم",
+ taskmgr_header_status: "الحالة",
+ taskmgr_header_type: "النوع",
+ terms: "الشروط",
+ text_document: "مستند نصي",
+ tos_fineprint:
+ "بالنقر على 'إنشاء حساب مجاني' فإنك توافق على {{link=terms}}شروط الخدمة{{/link}} و{{link=privacy}}سياسة الخصوصية{{/link}} لـPuter.",
+ transparency: "الشفافية",
+ trash: "المهملات",
+ two_factor: "المصادقة الثنائية",
+ two_factor_disabled: "تم تعطيل المصادقة الثنائية",
+ two_factor_enabled: "تم تمكين المصادقة الثنائية",
+ type: "نوع",
+ type_confirm_to_delete_account: "اكتب 'تأكيد' لحذف حسابك.",
+ ui_colors: "ألوان واجهة المستخدم",
+ ui_manage_sessions: "مدير الجلسات",
+ ui_revoke: "إلغاء",
+ undo: "تراجع",
+ unlimited: "غير محدود",
+ unzip: "فك الضغط",
+ upload: "رفع",
+ upload_here: "ارفع هنا",
+ usage: "الاستخدام",
+ username: "اسم المستخدم",
+ username_changed: "تم تحديث اسم المستخدم بنجاح.",
+ username_required: "اسم المستخدم مطلوب.",
+ versions: "الإصدارات",
+ videos: "مقاطع الفيديو",
+ visibility: "الرؤية",
+ yes: "نعم",
+ yes_release_it: "نعم، أطلقه",
+ you_have_been_referred_to_puter_by_a_friend:
+ "تم إحالتك إلى Puter بواسطة صديق!",
+ zip: "ضغط",
+ zipping_file: "جارٍ ضغط %strong%",
- // === 2FA Setup ===
- setup2fa_1_step_heading: "افتح تطبيق المصادقة الخاص بك",
- setup2fa_1_instructions: `
- يمكنك استخدام أي تطبيق مصادقة يدعم بروتوكول كلمة المرور لمرة واحدة المعتمدة على الوقت (TOTP).
- هناك العديد للاختيار من بينها، ولكن إذا كنت غير متأكد
- Authy
- هو خيار موثوق به لنظام Android و iOS.
- `,
- setup2fa_2_step_heading: "مسح رمز الاستجابة السريعة (QR code)",
- setup2fa_3_step_heading: "أدخل الرمز المكون من 6 أرقام",
- setup2fa_4_step_heading: "انسخ رموز الاسترداد الخاصة بك",
- setup2fa_4_instructions: `
- هذه رموز الاسترداد هي الطريقة الوحيدة للوصول إلى حسابك إذا فقدت هاتفك أو لم تتمكن من استخدام تطبيق المصادقة الخاص بك.
- تأكد من حفظها في مكان آمن.
- `,
- setup2fa_5_step_heading: "تأكيد إعداد المصادقة الثنائية (2FA)",
- setup2fa_5_confirmation_1: "لقد قمت بحفظ رموز الاسترداد في مكان آمن",
- setup2fa_5_confirmation_2: "أنا جاهز لتمكين المصادقة الثنائية (2FA)",
- setup2fa_5_button: "تمكين المصادقة الثنائية (2FA)",
+ // === 2FA Setup ===
+ setup2fa_1_step_heading: "افتح تطبيق المصادقة الخاص بك",
+ setup2fa_1_instructions: `
+ يمكنك استخدام أي تطبيق مصادقة يدعم بروتوكول كلمة المرور لمرة واحدة المعتمدة على الوقت (TOTP).
+ هناك العديد للاختيار من بينها، ولكن إذا كنت غير متأكد
+ Authy
+ هو خيار موثوق به لنظام Android و iOS.
+ `,
+ setup2fa_2_step_heading: "مسح رمز الاستجابة السريعة (QR code)",
+ setup2fa_3_step_heading: "أدخل الرمز المكون من 6 أرقام",
+ setup2fa_4_step_heading: "انسخ رموز الاسترداد الخاصة بك",
+ setup2fa_4_instructions: `
+ هذه رموز الاسترداد هي الطريقة الوحيدة للوصول إلى حسابك إذا فقدت هاتفك أو لم تتمكن من استخدام تطبيق المصادقة الخاص بك.
+ تأكد من حفظها في مكان آمن.
+ `,
+ setup2fa_5_step_heading: "تأكيد إعداد المصادقة الثنائية (2FA)",
+ setup2fa_5_confirmation_1: "لقد قمت بحفظ رموز الاسترداد في مكان آمن",
+ setup2fa_5_confirmation_2: "أنا جاهز لتمكين المصادقة الثنائية (2FA)",
+ setup2fa_5_button: "تمكين المصادقة الثنائية (2FA)",
- // === 2FA Login ===
- login2fa_otp_title: "أدخل رمز المصادقة الثنائية (2FA)",
- login2fa_otp_instructions:
- "أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة الخاص بك.",
- login2fa_recovery_title: "أدخل رمز الاسترداد",
- login2fa_recovery_instructions:
- "أدخل أحد رموز الاسترداد الخاصة بك للوصول إلى حسابك.",
- login2fa_use_recovery_code: "استخدام رمز الاسترداد",
- login2fa_recovery_back: "الرجوع",
- login2fa_recovery_placeholder: "XXXXXXXX",
+ // === 2FA Login ===
+ login2fa_otp_title: "أدخل رمز المصادقة الثنائية (2FA)",
+ login2fa_otp_instructions:
+ "أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة الخاص بك.",
+ login2fa_recovery_title: "أدخل رمز الاسترداد",
+ login2fa_recovery_instructions:
+ "أدخل أحد رموز الاسترداد الخاصة بك للوصول إلى حسابك.",
+ login2fa_use_recovery_code: "استخدام رمز الاسترداد",
+ login2fa_recovery_back: "الرجوع",
+ login2fa_recovery_placeholder: "XXXXXXXX",
change: "تغيير", // In English: "Change"
clock_visibility: "ظهور الساعة", // In English: "Clock Visibility"
@@ -382,8 +382,54 @@ const ar = {
"Share With…": "مشاركة مع...", // In English: "Share With…"
Owner: "المالك", // In English: "Owner"
"You can't share with yourself.": "لا يمكنك المشاركة مع نفسك.", // In English: "You can't share with yourself."
- "This user already has access to this item":
- "هذا المستخدم لديه بالفعل تحكم إلى هذا العنصر", // In English: "This user already has access to this item"
+ "This user already has access to this item": "هذا المستخدم لديه بالفعل تحكم إلى هذا العنصر", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "تغيير طريقة الدفع", // In English: "Change"
+ "billing.cancel": "إلغاء", // In English: "Cancel"
+ "billing.download_invoice": "تحميل", // In English: "Download"
+ "billing.payment_method": "طريقة الدفع", // In English: "Payment Method"
+ "billing.payment_method_updated": "تم تحديث طريقة الدفع", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "تأكيد طريقة الدفع", // In English: "Confirm Payment Method"
+ "billing.payment_history": "سجل الدفع", // In English: "Payment History"
+ "billing.refunded": "تم الاسترداد", // In English: "Refunded"
+ "billing.paid": "مدفوع", // In English: "Paid"
+ "billing.ok": "موافق", // In English: "OK"
+ "billing.resume_subscription": "استئناف الاشتراك", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "تم إلغاء اشتراكك", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "ستظل لديك إمكانية الوصول إلى اشتراكك حتى نهاية فترة الفوترة الحالية.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "مجاني", // In English: "Free"
+ "billing.offering.pro": "احترافي", // In English: "Professional"
+ "billing.offering.business": "تجاري", // In English: "Business"
+ "billing.cloud_storage": "Cloud Storage", // In English: "Cloud Storage"
+ "billing.ai_access": "التحصل على الذكاء الاصطناعي", // In English: "AI Access"
+ "billing.bandwidth": "Bandwidth", // In English: "Bandwidth"
+ "billing.apps_and_games": "التطبيقات والألعاب", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "الترقية إلى %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "التحويل إلى %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "إعداد طريقة الدفع", // In English: "Payment Setup"
+ "billing.back": "رجوع", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "أنت الآن مشترك في %strong% الفئة", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "أنت الآن مشترك", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "هل أنت متأكد من رغبتك في إلغاء اشتراكك؟", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "إعداد طريقة الاشتراك", // In English: "Subscription Setup"
+ "billing.cancel_it": "إلغاؤها", // In English: "Cancel It"
+ "billing.keep_it": "الاحتفاظ بها", // In English: "Keep It"
+ "billing.subscription_resumed": "تم استئناف اشتراكك %strong%!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "قم بالترقية الآن", // In English: "Upgrade Now"
+ "billing.upgrade": "ترقية", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "أنت حالياً على الفئة المجاني", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "تحميل التوصيل", // In English: "Download Receipt"
+ "billing.subscription_check_error": "حدثت مشكلة أثناء التحقق من حالة اشتراكك", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "لم يتم تأكيد بريدك الإلكتروني. سنرسل لك رمز التأكيد الآن", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "لقد ألغيت اشتراكك وستتحول تلقائياً إلى الفئة المجانية في نهاية فترة الفوترة. لن يتم فرض رسوم عليك مرة أخرى ما لم تعد الاشتراك", // 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": "خطتك الحالية حتى نهاية هذه الفترة الفوترة", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "الفئة الحالية", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "إلغاء الاشتراك", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "إدارة", // In English: "Manage"
+ "billing.limited": "محدود", // In English: "Limited"
+ "billing.expanded": "موسع", // In English: "Expanded"
+ "billing.accelerated": "مسرع", // In English: "Accelerated"
+ "billing.enjoy_msg": "استمتع بـ %% من Cloud Storage بالإضافة إلى مزايا أخرى.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
},
};
diff --git a/src/gui/src/i18n/translations/bn.js b/src/gui/src/i18n/translations/bn.js
index f2a67f374d..3a8c583729 100644
--- a/src/gui/src/i18n/translations/bn.js
+++ b/src/gui/src/i18n/translations/bn.js
@@ -366,7 +366,54 @@ const bn = {
"Share With…": "শেয়ার করুন…",
"Owner": "মালিক",
"You can't share with yourself.": "নিজের সাথে শেয়ার করতে পারবেন না।",
- "This user already has access to this item": "এই ব্যবহারকারীর ইতিমধ্যে এটাতে অ্যাক্সেস রয়েছে।"
+ "This user already has access to this item": "এই ব্যবহারকারীর ইতিমধ্যে এটাতে অ্যাক্সেস রয়েছে।",
+
+ "billing.change_payment_method": "পরিশোধ পদ্ধতি পরিবর্তন করুন",
+ "billing.cancel": "বাতিল করুন",
+ "billing.download_invoice": "চালান ডাউনলোড করুন",
+ "billing.payment_method": "পরিশোধ পদ্ধতি",
+ "billing.payment_method_updated": "পরিশোধ পদ্ধতি আপডেট হয়েছে!",
+ "billing.confirm_payment_method": "পরিশোধ পদ্ধতি নিশ্চিত করুন",
+ "billing.payment_history": "পরিশোধ ইতিহাস",
+ "billing.refunded": "ফেরত দেওয়া হয়েছে",
+ "billing.paid": "পরিশোধিত",
+ "billing.ok": "ঠিক আছে",
+ "billing.resume_subscription": "সাবস্ক্রিপশন পুনরায় চালু করুন",
+ "billing.subscription_cancelled": "আপনার সাবস্ক্রিপশন বাতিল করা হয়েছে।",
+ "billing.subscription_cancelled_description": "এই বিলিং পিরিয়ডের শেষ পর্যন্ত আপনি আপনার সাবস্ক্রিপশনটি ব্যবহার করতে পারবেন।",
+ "billing.offering.free": "বিনামূল্য",
+ "billing.offering.pro": "প্রফেশনাল",
+ "billing.offering.business": "ব্যবসায়িক",
+ "billing.cloud_storage": "ক্লাউড স্টোরেজ",
+ "billing.ai_access": "এআই অ্যাক্সেস",
+ "billing.bandwidth": "ব্যান্ডউইথ",
+ "billing.apps_and_games": "অ্যাপস এবং গেমস",
+ "billing.upgrade_to_pro": "%strong%-এ আপগ্রেড করুন",
+ "billing.switch_to": "%strong%-এ পরিবর্তন করুন",
+ "billing.payment_setup": "পরিশোধ সেটআপ",
+ "billing.back": "পেছনে যান",
+ "billing.you_are_now_subscribed_to": "আপনি এখন %strong% স্তরের সাবস্ক্রাইবার।",
+ "billing.you_are_now_subscribed_to_without_tier": "আপনি এখন সাবস্ক্রাইবার।",
+ "billing.subscription_cancellation_confirmation": "আপনি কি নিশ্চিত যে আপনি আপনার সাবস্ক্রিপশন বাতিল করতে চান?",
+ "billing.subscription_setup": "সাবস্ক্রিপশন সেটআপ",
+ "billing.cancel_it": "বাতিল করুন",
+ "billing.keep_it": "রাখুন",
+ "billing.subscription_resumed": "আপনার %strong% সাবস্ক্রিপশন পুনরায় চালু করা হয়েছে!",
+ "billing.upgrade_now": "এখনই আপগ্রেড করুন",
+ "billing.upgrade": "আপগ্রেড করুন",
+ "billing.currently_on_free_plan": "আপনি বর্তমানে ফ্রি প্ল্যানে আছেন।",
+ "billing.download_receipt": "রসিদ ডাউনলোড করুন",
+ "billing.subscription_check_error": "আপনার সাবস্ক্রিপশন স্ট্যাটাস চেক করার সময় একটি সমস্যা দেখা দিয়েছে।",
+ "billing.email_confirmation_needed": "আপনার ইমেল নিশ্চিত করা হয়নি। আমরা এখনই এটি নিশ্চিত করার জন্য একটি কোড পাঠাব।",
+ "billing.sub_cancelled_but_valid_until": "আপনি আপনার সাবস্ক্রিপশন বাতিল করেছেন এবং এটি বিলিং পিরিয়ডের শেষে স্বয়ংক্রিয়ভাবে ফ্রি স্তরে পরিবর্তিত হবে। আপনি পুনরায় সাবস্ক্রাইব না করা পর্যন্ত আর চার্জ হবে না।",
+ "billing.current_plan_until_end_of_period": "এই বিলিং পিরিয়ডের শেষ পর্যন্ত আপনার বর্তমান প্ল্যান।",
+ "billing.current_plan": "বর্তমান প্ল্যান",
+ "billing.cancelled_subscription_tier": "বাতিল করা সাবস্ক্রিপশন (%%)",
+ "billing.manage": "পরিচালনা করুন",
+ "billing.limited": "সীমিত",
+ "billing.expanded": "বিস্তৃত",
+ "billing.accelerated": "ত্বরান্বিত",
+ "billing.enjoy_msg": "%% ক্লাউড স্টোরেজ এবং অন্যান্য সুবিধা উপভোগ করুন।"
},
};
diff --git a/src/gui/src/i18n/translations/br.js b/src/gui/src/i18n/translations/br.js
index 310475bc0f..3524b98ef5 100644
--- a/src/gui/src/i18n/translations/br.js
+++ b/src/gui/src/i18n/translations/br.js
@@ -361,6 +361,53 @@ const br = {
"Owner": "Proprietário", // In English: "Owner"
"You can't share with yourself.": "Vocẽ não pode compartilhar com você mesmo", // In English: "You can't share with yourself."
"This user already has access to this item": "Esse usuário já tem acesso a esse item", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "Mudar", // In English: "Change"
+ "billing.cancel": "Cancelar", // In English: "Cancel"
+ "billing.download_invoice": "Baixar", // In English: "Download"
+ "billing.payment_method": "Método de Pagamento", // In English: "Payment Method"
+ "billing.payment_method_updated": "Método de Pagamento Atualizado!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Confirmar Método de Pagamento", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Histórico de Pagamento", // In English: "Payment History"
+ "billing.refunded": "Reembolsado", // In English: "Refunded"
+ "billing.paid": "Pago", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Continuar Assinatura", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Sua assinatura foi cancelada", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Você ainda terá acesso a sua assinatura até finalizar o período pago", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Grátis", // In English: "Free"
+ "billing.offering.pro": "Profissional", // In English: "Professional"
+ "billing.offering.business": "Business", // In English: "Business"
+ "billing.cloud_storage": "Armazenamento em Nuvem", // In English: "Cloud Storage"
+ "billing.ai_access": "Acesso à IA", // In English: "AI Access"
+ "billing.bandwidth": "Largura de banda", // In English: "Bandwidth"
+ "billing.apps_and_games": "Apps e Games", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Atualize para %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Mudar para %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Configuração de Pagamento", // In English: "Payment Setup"
+ "billing.back": "Voltar", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Agora você está inscrito no plano %strong%.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Você agora está inscrito.", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Você tem certeza que quer cancelar sua assinatura ?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Configuração de assinatura", // In English: "Subscription Setup"
+ "billing.cancel_it": "Cancelar", // In English: "Cancel It"
+ "billing.keep_it": "Mantenha isso", // In English: "Keep It"
+ "billing.subscription_resumed": "Seu plano %strong% foi renovado!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Atualizar Agora", // In English: "Upgrade Now"
+ "billing.upgrade": "Atualizar", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Você atualmente esta usando o plano grátis.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Baixar Comprovante", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Ocorreu um problema ao verificar o status da sua assinatura.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Seu e-mail não foi confirmado. Enviaremos um código para confirmá-lo agora.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Você cancelou sua assinatura e ela será automaticamente revertida para o plano gratuito no final do período de cobrança. Você não será cobrado novamente, a menos que reative a assinatura.", // 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": "Seu plano atual até o final deste período de cobrança.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Plano atual", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Assinatura Cancelada (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Gerenciar", // In English: "Manage"
+ "billing.limited": "Limitado", // In English: "Limited"
+ "billing.expanded": "Expandido", // In English: "Expanded"
+ "billing.accelerated": "Aprimorado", // In English: "Accelerated"
+ "billing.enjoy_msg": "Aproveite %% de Armazenamento em Nuvem, além de outros benefícios.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/da.js b/src/gui/src/i18n/translations/da.js
index 399282799a..f49365e522 100644
--- a/src/gui/src/i18n/translations/da.js
+++ b/src/gui/src/i18n/translations/da.js
@@ -18,354 +18,395 @@
*/
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 med tvang?',
+ 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: 'Behold 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 opbevare 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 ændringer.',
+ 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: 'Sessionshåndtering',
+ 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"
+ // === 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: 'Skift',
+ clock_visibility: 'Ur synlighed',
+ reading: 'Læser %strong%',
+ writing: 'Skriver %strong%',
+ unzipping: 'Udpakker %strong%',
+ sequencing: 'Sekventerer %strong%',
+ zipping: 'Zipper %strong%',
+ Editor: 'Redaktør',
+ Viewer: 'Seer',
+ People_with_access: 'Personer med adgang',
+ Share_With: 'Del med…',
+ Owner: 'Ejer',
+ You_cant_share_with_yourself: 'Du kan ikke dele med dig selv.',
+ This_user_already_has_access_to_this_item: 'Denne bruger har allerede adgang til dette element',
+ billing_change_payment_method: 'Skift betalingsmetode',
+ billing_cancel: 'Annuller',
+ billing_download_invoice: 'Download faktura',
+ billing_payment_method: 'Betalingsmetode',
+ billing_payment_method_updated: 'Betalingsmetode opdateret!',
+ billing_confirm_payment_method: 'Bekræft betalingsmetode',
+ billing_payment_history: 'Betalingshistorik',
+ billing_refunded: 'Refunderet',
+ billing_paid: 'Betalt',
+ billing_ok: 'OK',
+ billing_resume_subscription: 'Genoptag abonnement',
+ billing_subscription_cancelled: 'Dit abonnement er blevet annulleret.',
+ billing_subscription_cancelled_description: 'Du vil stadig have adgang til dit abonnement indtil slutningen af denne faktureringsperiode.',
+ billing_offering_free: 'Gratis',
+ billing_offering_pro: 'Professionel',
+ billing_offering_business: 'Forretning',
+ billing_cloud_storage: 'Cloud-lager',
+ billing_ai_access: 'AI-adgang',
+ billing_bandwidth: 'Båndbredde',
+ billing_apps_and_games: 'Apps & Spil',
+ billing_upgrade_to_pro: 'Opgrader til %strong%',
+ billing_switch_to: 'Skift til %strong%',
+ billing_payment_setup: 'Betalingsopsætning',
+ billing_back: 'Tilbage',
+ billing_you_are_now_subscribed_to: 'Du er nu abonneret på %strong% niveau.',
+ billing_you_are_now_subscribed_to_without_tier: 'Du har nu et abonnement',
+ billing_subscription_cancellation_confirmation: 'Er du sikker på, at du vil annullere dit abonnement?',
+ billing_subscription_setup: 'Abonnementsopsætning',
+ billing_cancel_it: 'Annuller det',
+ billing_keep_it: 'Behold det',
+ billing_subscription_resumed: 'Dit %strong% abonnement er blevet genoptaget!',
+ billing_upgrade_now: 'Opgrader nu',
+ billing_upgrade: 'Opgrader',
+ billing_currently_on_free_plan: 'Du bruger i øjeblikket den gratis version.',
+ billing_download_receipt: 'Download kvittering',
+ billing_subscription_check_error: 'Der opstod et problem under kontrol af din abonnementsstatus.',
+ billing_email_confirmation_needed: 'Din e-mail er ikke blevet bekræftet. Vi sender dig en kode for at bekræfte den nu.',
+ billing_sub_cancelled_but_valid_until: 'Du har annulleret dit abonnement, og det vil automatisk skifte til den gratis plan ved slutningen af faktureringsperioden. Du vil ikke blive opkrævet igen, medmindre du genabonnerer.',
+ billing_current_plan_until_end_of_period: 'Din nuværende plan indtil slutningen af denne faktureringsperiode.',
+ billing_current_plan: 'Nuværende plan',
+ billing_cancelled_subscription_tier: 'Annulleret abonnement (%%)',
+ billing_manage: 'Administrer',
+ billing_limited: 'Begrænset',
+ billing_expanded: 'Udvidet',
+ billing_accelerated: 'Accelereret',
+ billing_enjoy_msg: 'Nyd %% af Cloud-lager plus andre fordele.',
}
-
};
export default da;
diff --git a/src/gui/src/i18n/translations/de.js b/src/gui/src/i18n/translations/de.js
index 6f709fa35d..fd4f8dc1d4 100644
--- a/src/gui/src/i18n/translations/de.js
+++ b/src/gui/src/i18n/translations/de.js
@@ -362,6 +362,52 @@ const de = {
"You can't share with yourself.": "Du kannst nicht mit dir selbst teilen.", // In English: "You can't share with yourself."
"This user already has access to this item": "Dieser Benutzer hat bereits Zugriff auf dieses Element", // In English: "This user already has access to this item"
+ "billing.change_payment_method": "Ändern", // In English: "Change"
+ "billing.cancel": "Abbrechen", // In English: "Cancel"
+ "billing.download_invoice": "Herunterladen", // In English: "Download"
+ "billing.payment_method": "Zahlungsmethoden", // In English: "Payment Method"
+ "billing.payment_method_updated": "Zahlungsmethoden wurden aktualisiert!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Zahlungsmethode bestätigen", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Zahlungshistorie", // In English: "Payment History"
+ "billing.refunded": "Rückerstattung", // In English: "Refunded"
+ "billing.paid": "Bezahlt", // In English: "Paid"
+ "billing.ok": "Ok", // In English: "OK"
+ "billing.resume_subscription": "Abonnement fortsetzen", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Dein Abonnement wurde gekündigt.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Bis zum Ende des Abrechnungszeitraums haben Sie weiterhin Zugriff auf Ihr Abonnement.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Kostenlos", // In English: "Free"
+ "billing.offering.pro": "Professionell", // In English: "Professional"
+ "billing.offering.business": "Unternehmen", // In English: "Business"
+ "billing.cloud_storage": "Cloud Speicher", // In English: "Cloud Storage"
+ "billing.ai_access": "KI Zugang", // In English: "AI Access"
+ "billing.bandwidth": "Bandbreite", // In English: "Bandwidth"
+ "billing.apps_and_games": "Apps & Spiele", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Aktualisieren auf %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Wechseln auf %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Einrichtung der Zahlung", // In English: "Payment Setup"
+ "billing.back": "Zurück", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Sie haben jetzt den %strong% Plan abonniert.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Sie haben jetzt abonniert", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Sind Sie sicher, dass Sie Ihr Abonnement kündigen möchten?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Abonnement Einrichtung", // In English: "Subscription Setup"
+ "billing.cancel_it": "Abbrechen", // In English: "Cancel It"
+ "billing.keep_it": "Behalten", // In English: "Keep It"
+ "billing.subscription_resumed": "Ihr %strong%-Abonnement wurde fortgesetzt!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Jetzt aktualisieren", // In English: "Upgrade Now"
+ "billing.upgrade": "Aktualisieren", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Sie sind derzeit im kostenlosen Abonnement.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Quittung herunterladen", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Bei der Überprüfung Ihres Abonnementstatus ist ein Problem aufgetreten.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Ihre E-Mail wurde noch nicht bestätigt. Wir senden Ihnen jetzt einen Code zur Bestätigung.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Sie haben Ihr Abonnement gekündigt und es wird am Ende des Abrechnungszeitraums automatisch auf die kostenlose Version umgestellt. Es wird Ihnen nicht erneut in Rechnung gestellt, es sei denn, Sie abonnieren es erneut.", // 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": "Ihr aktueller Plan bis zum Ende dieses Abrechnungszeitraums.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Aktueller Plan", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Abonnement abbrechen (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Verwalten", // In English: "Manage"
+ "billing.limited": "Begrenzt", // In English: "Limited"
+ "billing.expanded": "Erweitert", // In English: "Expanded"
+ "billing.accelerated": "Beschleunigt", // In English: "Accelerated"
+ "billing.enjoy_msg": "Genießen Sie %% des Cloud-Speichers und weitere Vorteile.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/en.js b/src/gui/src/i18n/translations/en.js
index abb459da68..613120afc0 100644
--- a/src/gui/src/i18n/translations/en.js
+++ b/src/gui/src/i18n/translations/en.js
@@ -372,13 +372,43 @@ const en = {
'billing.refunded': "Refunded",
'billing.paid': "Paid",
'billing.ok': "OK",
- 'billing.confirm_payment_method': 'Confirm Payment Method',
'billing.resume_subscription': 'Resume Subscription',
'billing.subscription_cancelled': 'Your subscription has been canceled.',
'billing.subscription_cancelled_description': 'You will still have access to your subscription until the end of this billing period.',
'billing.offering.free': 'Free',
'billing.offering.pro': 'Professional',
'billing.offering.business': 'Business',
+ 'billing.cloud_storage': 'Cloud Storage',
+ 'billing.ai_access': 'AI Access',
+ 'billing.bandwidth': 'Bandwidth',
+ 'billing.apps_and_games': 'Apps & Games',
+ 'billing.upgrade_to_pro': 'Upgrade to %strong%',
+ 'billing.switch_to': 'Switch to %strong%',
+ 'billing.payment_setup': 'Payment Setup',
+ 'billing.back': 'Back',
+ 'billing.you_are_now_subscribed_to': 'You are now subscribed to %strong% tier.',
+ 'billing.you_are_now_subscribed_to_without_tier': 'You are now subscribed',
+ 'billing.subscription_cancellation_confirmation': 'Are you sure you want to cancel your subscription?',
+ 'billing.subscription_setup': 'Subscription Setup',
+ 'billing.cancel_it': 'Cancel It',
+ 'billing.keep_it': 'Keep It',
+ 'billing.subscription_resumed': 'Your %strong% subscription has been resumed!',
+ 'billing.upgrade_now': 'Upgrade Now',
+ 'billing.upgrade': 'Upgrade',
+ 'billing.currently_on_free_plan': 'You are currently on the free plan.',
+ 'billing.download_receipt': 'Download Receipt',
+ 'billing.subscription_check_error': 'A problem occurred while checking your subscription status.',
+ 'billing.payment_method_updated': 'Payment method updated!',
+ 'billing.email_confirmation_needed': 'Your email has not been confirmed. We\'ll send you a code to confirm it now.',
+ 'billing.sub_cancelled_but_valid_until': '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': 'Your current plan until the end of this billing period.',
+ 'billing.current_plan': 'Current plan',
+ 'billing.cancelled_subscription_tier': 'Cancelled Subscription (%%)',
+ 'billing.manage': 'Manage',
+ 'billing.limited': 'Limited',
+ 'billing.expanded': 'Expanded',
+ 'billing.accelerated': 'Accelerated',
+ 'billing.enjoy_msg': 'Enjoy %% of Cloud Storage plus other benefits.',
}
};
diff --git a/src/gui/src/i18n/translations/es.js b/src/gui/src/i18n/translations/es.js
index 63cf1b16ec..4ff34cfe8e 100644
--- a/src/gui/src/i18n/translations/es.js
+++ b/src/gui/src/i18n/translations/es.js
@@ -18,357 +18,450 @@
*/
/**
- * Traslation notes:
+ * Traslation notes:
* - Change all "Email" to "Correo electrónico"
* - puter_description the most acurated translation for "privacy-first personal cloud" I could think of is "servicio de nube personal enfocado en privacidad"
* - plural_suffix: 's' has no direct translation to spanish. There are multiple plural suffix in spanish 'as' || "es" || "os || "s". Leave "s" as it is only been used on item: 'elemento' and will end up as 'elementos'
-*/
+ */
const es = {
- name: "Español",
- english_name: "Spanish",
- code: "es",
- dictionary: {
- about: "Acerca De",
- account: "Cuenta",
- account_password: "Verifica Contraseña De La Cuenta",
- access_granted_to: "Acceso Permitido A",
- add_existing_account: "Añadir una cuenta existente",
- all_fields_required: 'Todos los campos son obligatorios.',
- allow: 'Permitir',
- apply: "Aplicar",
- ascending: 'Ascendiente',
- associated_websites: "Sitios Web Asociados",
- auto_arrange: 'Organización Automática',
- background: "Fondo",
- browse: "Buscar",
- cancel: 'Cancelar',
- center: 'Centrar',
- change_desktop_background: 'Cambiar el fondo de pantalla…',
- change_email: "Cambiar Correo Electrónico",
- change_language: "Cambiar Idioma",
- change_password: "Cambiar Contraseña",
- change_ui_colors: "Cambiar colores de la interfaz",
- change_username: "Cambiar Nombre de Usuario",
- close: 'Cerrar',
- close_all_windows: "Cerrar todas las ventanas",
- close_all_windows_confirm: "¿Estás seguro de que quieres cerrar todas las ventanas?",
- close_all_windows_and_log_out: 'Cerrar ventanas y cerrar sesión',
- change_always_open_with: "¿Quieres abrir siempre este tipo de archivos con",
- color: 'Color',
- confirm: 'Confirmar',
- confirm_2fa_setup: 'He añadido el código a mi aplicación de autenticación',
- confirm_2fa_recovery: 'He guardado mis códigos de recuperación en un lugar seguro',
- confirm_account_for_free_referral_storage_c2a: 'Crea una cuenta y confirma tu correo electrónico para recibir 1 GB de almacenamiento gratuito. Tu amigo recibirá 1 GB de almacenamiento gratuito también.',
- confirm_code_generic_incorrect: "Código incorrecto.",
- confirm_code_generic_too_many_requests: "Too many requests. Please wait a few minutes.",
- confirm_code_generic_submit: "Enviar código",
- confirm_code_generic_try_again: "Intenta nuevamente",
- confirm_code_generic_title: "Enter Confirmation Code",
- confirm_code_2fa_instruction: "Ingresa los 6 dígitos de tu aplicación de autenticación.",
- confirm_code_2fa_submit_btn: "Enviar",
- confirm_code_2fa_title: "Ingrese el código de 2FA",
- confirm_delete_multiple_items: '¿Estás seguro de que quieres eliminar permanentemente estos elementos?',
- confirm_delete_single_item: '¿Quieres eliminar este elemento permanentemente?',
- confirm_open_apps_log_out: 'Tienes aplicaciones abiertas.¿Estás seguro de que quieres cerrar sesión?',
- confirm_new_password: "Confirma la Nueva Contraseña",
- confirm_delete_user: "¿Estás seguro que quieres borrar esta cuenta? Todos tus archivos e información serán borrados permanentemente. Esta acción no se puede deshacer.",
- confirm_delete_user_title: "¿Eliminar cuenta?",
- confirm_session_revoke: "¿Estás seguro de que quieres revocar esta sesión?",
- confirm_your_email_address: "Confirma tu dirección de correo electrónico",
- contact_us: "Contáctanos",
- contact_us_verification_required: "Debes tener un correo electrónico verificado para usar esto.",
- contain: 'Contiene',
- continue: "Continuar",
- copy: 'Copiar',
- copy_link: "Copiar Enlace",
- copying: "Copiando",
- copying_file: "Copiando %%",
- cover: 'Cubrir',
- create_account: "Crear una cuenta",
- create_free_account: "Crear una cuenta gratuita",
- create_shortcut: "Crear un acceso directo",
- credits: "Creditos",
- current_password: "Contraseña actual",
- cut: 'Cortar',
- clock: "Reloj",
- clock_visible_hide: 'Ocultar - Siempre oculto',
- clock_visible_show: 'Mostrar - Siempre visible',
- clock_visible_auto: 'Auto - Por defecto, visible solo en modo pantalla completa.',
- close_all: 'Cerrar todo',
- created: 'Creado',
- date_modified: 'Fecha de modificación',
- default: 'Por defecto',
- delete: 'Borrar',
- delete_account: "Borrar cuenta",
- delete_permanently: "Borrar permanentemente",
- deleting_file: "Eliminando %%",
- deploy_as_app: 'Desplegar como una aplicación',
- descending: 'Descendiente',
- desktop: 'Escritorio',
- desktop_background_fit: "Ajustar",
- developers: "Desarrolladores",
- dir_published_as_website: `%strong% ha sido publicado en:`,
- disable_2fa: 'Deshabilitar 2FA',
- disable_2fa_confirm: "¿Estás seguro que quieres deshabilitar 2FA?",
- disable_2fa_instructions: "Ingresa tu contraseña para deshabilitar 2FA.",
- disassociate_dir: "Desvincular directorio",
- documents: 'Documentos',
- dont_allow: 'No permitir',
- download: 'Descargar',
- download_file: 'Descargar archivo',
- downloading: "Descargando",
- email: "Correo electrónico",
- email_change_confirmation_sent: "Se ha enviado un mensaje de confirmación a tu nueva dirección de correo electrónico. Por favor, revisa tu bandeja de entrada y sigue las instrucciónes para completar el proceso.",
- email_invalid: 'El correo electrónico no es válido.',
- email_or_username: "Correo electrónico o Nombre de Usuario",
- email_required: 'El correo electrónico es obligatorio.',
- empty_trash: 'Vaciar la papelera',
- empty_trash_confirmation: `¿Estás seguro de que quieres borrar permanentemente todos los elementos de la Papelera?`,
- emptying_trash: 'Vaciando la papelera…',
- enable_2fa: 'Habilitar 2FA',
- end_hard: "Finalizar abruptamente",
- end_process_force_confirm: "¿Estás seguro de que quieres forzar la salida de este proceso?",
- end_soft: "Finalizar suavemente",
- enlarged_qr_code: "Código QR ampliado",
- enter_password_to_confirm_delete_user: "Ingresa tu contraseña para confirmar la eliminación de la cuenta",
- error_message_is_missing: "Falta el mensaje de error.",
- error_unknown_cause: "Un error desconocido a ocurrido.",
- error_uploading_files: "Error al subir archivos",
- favorites: "Favoritos",
- feedback: "Sugerencias",
- feedback_c2a: "Por favor, usa el formulario para enviarnos tus sugerencias, comentarios y reporte de errores.",
- feedback_sent_confirmation: "Gracias por ponerte en contacto con nosotros. Si tienes un correo electrónico vinculado a esta cuenta, nos pondremos en contacto contigo tan pronto como podamos.",
- fit: "Ajustar",
- folder: 'Carpeta',
- force_quit: 'Forzar cierre',
- forgot_pass_c2a: "¿Olvidaste tu contraseña?",
- from: "De",
- general: "General",
- get_a_copy_of_on_puter: `¡Consigue una copia de '%%' en Puter.com!`,
- get_copy_link: 'Copiar el enlace',
- hide_all_windows: "Ocultar todas las ventanas",
- home: 'Inicio',
- html_document: 'Documento HTML',
- hue: 'Hue',
- image: 'Imagen',
- incorrect_password: "Contraseña incorrecta",
- invite_link: "Enlace de invitación",
- item: 'elemento',
- items_in_trash_cannot_be_renamed: `Este elemento no se puede renombrar porque está en la papelera. Para cambiar el nombre de este archivo, primero extráelo fuera de la misma.`,
- jpeg_image: 'Imagen JPEG',
- keep_in_taskbar: 'Mantener en la barra de tareas',
- language: "Lenguage",
- license: "Licencia",
- lightness: 'Claridad',
- link_copied: "Enlace copiado",
- loading: 'Cargando',
- log_in: "Iniciar sesión",
- log_into_another_account_anyway: 'Iniciar sesión en otra cuenta de todos modos',
- log_out: 'Cerrar sesión',
- looks_good: "Se ve bien!",
- manage_sessions: "Administrar sesión",
- menubar_style: "Estilo de la barra de menú",
- menubar_style_desktop: "Escritorio",
- menubar_style_system: "Sistema",
- menubar_style_window: "Ventana",
- modified: 'Modified',
- move: 'Mover',
- moving_file: "Moviendo %%",
- my_websites: "Mis páginas web",
- name: 'Nombre',
- name_cannot_be_empty: 'El nombre no puede estar vacío.',
- name_cannot_contain_double_period: "El nombre no puede ser el carácter '..'.",
- name_cannot_contain_period: "El nombre no puede ser el carácter '.'.",
- name_cannot_contain_slash: "El nombre no puede contener el carácter '/'.",
- name_must_be_string: "El nombre debe ser una cadena de texto.",
- name_too_long: `El nombre no puede tener más de %% caracteres.`,
- new: 'Nuevo',
- new_email: 'Nuevo correo electrónico',
- new_folder: 'Nueva carpeta',
- new_password: "Nueva contraseña",
- new_username: "Nuevo nombre de usuario",
- no: 'No',
- no_dir_associated_with_site: 'No hay un directorio vinculado con esta dirección.',
- no_websites_published: "Aun no has publicado ningún sitio web. Haz click derecho en una carpeta para empezar",
- ok: 'OK',
- open: "Abrir",
- open_in_new_tab: "Abrir en una nueva pestaña",
- open_in_new_window: "Abrir en una nueva ventana",
- open_with: "Abrir con",
- original_name: 'Nombre original',
- original_path: 'Ruta original',
- oss_code_and_content: "Software y contenido de código abierto",
- password: "Contraseña",
- password_changed: "Contraseña cambiada.",
- password_recovery_rate_limit: "Haz alcanzado nuestra tasa de refresco; por favor espera unos minutos. Para evitar esto en el futuro, evita refrescar la página muchas veces.",
- password_recovery_token_invalid: "La contraseña de token de recuperación ya no es válida.",
- password_recovery_unknown_error: "Ocurrió un error desconocido. Por favor, inténtalo de nuevo más tarde.",
- password_required: 'La contraseña es obligatoria.',
- password_strength_error: "La contraseña debe tener almenos 8 caracteres de largo y contener almenos una letra mayúscula, una minúscula, un numero, y un caracter especial.",
- passwords_do_not_match: '`Nueva Contraseña` y `Confirmar Nueva Contraseña` no coinciden.',
- paste: 'Pegar',
- paste_into_folder: "Pegar en la Carpeta",
- path: 'Ruta',
- personalization: "Personalización",
- pick_name_for_website: "Escoge un nombre para tu página web:",
- picture: "Imagen",
- pictures: 'Imagenes',
- plural_suffix: 's',
- powered_by_puter_js: `Creado por {{link=docs}}Puter.js{{/link}}`,
- preparing: "Preparando...",
- preparing_for_upload: "Preparando para la subida...",
- print: 'Imprimir',
- privacy: "Privacidad",
- proceed_to_login: 'Procede a iniciar sesión',
- proceed_with_account_deletion: "Procede con la eliminación de la cuenta",
- process_status_initializing: "Inicializando",
- process_status_running: "El ejecución",
- process_type_app: 'Aplicación',
- process_type_init: 'Inicialización',
- process_type_ui: 'Interfaz de usuario',
- properties: "Propiedades",
- public: 'Publico',
- publish: "Publicar",
- publish_as_website: 'Publicar como página web',
- puter_description: `Puter es un servicio de nube personal enfocado en privacidad que mantiene tus archivos, aplicaciónes, y juegos en un solo lugar, accesible desde cualquier lugar en cualquier momento.`,
- reading_file: "Leyendo %strong%",
- recent: "Reciente",
- recommended: "Recomendado",
- recover_password: "Recuperar Contraseña",
- refer_friends_c2a: "Consigue 1 GB por cada amigo que cree y confirme una cuenta en Puter ¡Tu amigo recibirá 1GB también!",
- refer_friends_social_media_c2a: `¡Consigue 1 GB de almacenamiento gratuito en Puter.com!`,
- refresh: 'Refrescar',
- release_address_confirmation: `¿Estás seguro de que quieres liberar esta dirección?`,
- remove_from_taskbar:'Eliminar de la barra de tareas',
- rename: 'Renombrar',
- repeat: 'Repetir',
- replace: 'Remplazar',
- replace_all: 'Replace All',
- resend_confirmation_code: "Reenviar Código de Confirmación",
- reset_colors: "Restablecer colores",
- restart_puter_confirm: "¿Estás seguro que deseas reiniciar Puter?",
- restore: "Restaurar",
- save: 'Guardar',
- saturation: 'Saturación',
- save_account: 'Guardar cuenta',
- save_account_to_get_copy_link: "Por favor, crea una cuenta para continuar.",
- save_account_to_publish: 'Por favor, crea una cuenta para continuar.',
- save_session: 'Guardar sesión',
- save_session_c2a: 'Crea una cuenta para guardar tu sesión actual y evitar así perder tu trabajo.',
- scan_qr_c2a: 'Escanee el código a continuación para inicia sesión desde otros dispositivos',
- scan_qr_2fa: 'Escanee el codigo QR con su aplicación de autenticación',
- scan_qr_generic: 'Scan this QR code using your phone or another device',
- search: 'Buscar',
- seconds: 'segundos',
- security: "Seguridad",
- select: "Seleccionar",
- selected: 'seleccionado',
- select_color: 'Seleccionar color…',
- sessions: "Sesión",
- send: "Enviar",
- send_password_recovery_email: "Enviar la contraseña al correo de recuperación",
- session_saved: "Gracias por crear una cuenta. La sesión ha sido guardada.",
- set_new_password: "Establecer una nueva contraseña",
- settings: "Opciones",
- share: "Compartir",
- share_to: "Compartir a",
- share_with: "Compartir con:",
- shortcut_to: "Acceso directo a",
- show_all_windows: "Mostrar todas las ventanas",
- show_hidden: 'Mostrar ocultos',
- sign_in_with_puter: "Inicia sesión con Puter",
- sign_up: "Registrarse",
- signing_in: "Registrándose…",
- size: 'Tamaño',
- skip: 'Saltar',
- something_went_wrong: "Algo salió mal.",
- sort_by: 'Ordenar Por',
- start: 'Inicio',
- status: "Estado",
- storage_usage: "Uso del almacenamiento",
- storage_puter_used: 'Usado por Puter',
- taking_longer_than_usual: 'Tardando un poco más de lo habitual. Por favor, espere...',
- task_manager: "Administrador de tareas",
- taskmgr_header_name: "Nombre",
- taskmgr_header_status: "Estado",
- taskmgr_header_type: "Tipo",
- terms: "Terminos",
- text_document: 'Documento de Texto',
- tos_fineprint: `Al hacer clic en 'Crear una cuenta gratuita' aceptas los {{link=terms}}términos del servicio{{/link}} y {{link=privacy}}la política de privacidad{{/link}} de Puter.`,
- transparency: "Transparencia",
- trash: 'Papelera',
- two_factor: 'Autenticación de dos factores',
- two_factor_disabled: '2FA Deshabilitadp',
- two_factor_enabled: '2FA Habilitado',
- type: 'Tipo',
- type_confirm_to_delete_account: "Ingrese 'Confirmar' para borrar esta cuenta.",
- ui_colors: "Colores de interfaz",
- ui_manage_sessions: "Administrador de sesión",
- ui_revoke: "Revocar",
- undo: 'Deshacer',
- unlimited: 'Ilimitado',
- unzip: "Descomprimir",
- upload: 'Subir',
- upload_here: 'Subir aquí',
- usage: 'Uso',
- username: "Nombre de usuario",
- username_changed: 'Nombre de usuario actualizado correctamente.',
- username_required: 'El nombre de usuario es obligatorio.',
- versions: "Versiones",
- videos: 'Videos',
- visibility: 'Visibilidad',
- yes: 'Si',
- yes_release_it: 'Sí, libéralo',
- you_have_been_referred_to_puter_by_a_friend: "¡Has sido invitado a Puter por un amigo!",
- zip: "Zip",
- zipping_file: "Compriminendo %strong%",
+ name: 'Español',
+ english_name: 'Spanish',
+ code: 'es',
+ dictionary: {
+ about: 'Acerca De',
+ account: 'Cuenta',
+ account_password: 'Verifica Contraseña De La Cuenta',
+ access_granted_to: 'Acceso Permitido A',
+ add_existing_account: 'Añadir una cuenta existente',
+ all_fields_required: 'Todos los campos son obligatorios.',
+ allow: 'Permitir',
+ apply: 'Aplicar',
+ ascending: 'Ascendiente',
+ associated_websites: 'Sitios Web Asociados',
+ auto_arrange: 'Organización Automática',
+ background: 'Fondo',
+ browse: 'Buscar',
+ cancel: 'Cancelar',
+ center: 'Centrar',
+ change_desktop_background: 'Cambiar el fondo de pantalla…',
+ change_email: 'Cambiar Correo Electrónico',
+ change_language: 'Cambiar Idioma',
+ change_password: 'Cambiar Contraseña',
+ change_ui_colors: 'Cambiar colores de la interfaz',
+ change_username: 'Cambiar Nombre de Usuario',
+ close: 'Cerrar',
+ close_all_windows: 'Cerrar todas las ventanas',
+ close_all_windows_confirm:
+ '¿Estás seguro de que quieres cerrar todas las ventanas?',
+ close_all_windows_and_log_out: 'Cerrar ventanas y cerrar sesión',
+ change_always_open_with: '¿Quieres abrir siempre este tipo de archivos con',
+ color: 'Color',
+ confirm: 'Confirmar',
+ confirm_2fa_setup: 'He añadido el código a mi aplicación de autenticación',
+ confirm_2fa_recovery:
+ 'He guardado mis códigos de recuperación en un lugar seguro',
+ confirm_account_for_free_referral_storage_c2a:
+ 'Crea una cuenta y confirma tu correo electrónico para recibir 1 GB de almacenamiento gratuito. Tu amigo recibirá 1 GB de almacenamiento gratuito también.',
+ confirm_code_generic_incorrect: 'Código incorrecto.',
+ confirm_code_generic_too_many_requests:
+ 'Too many requests. Please wait a few minutes.',
+ confirm_code_generic_submit: 'Enviar código',
+ confirm_code_generic_try_again: 'Intenta nuevamente',
+ confirm_code_generic_title: 'Enter Confirmation Code',
+ confirm_code_2fa_instruction:
+ 'Ingresa los 6 dígitos de tu aplicación de autenticación.',
+ confirm_code_2fa_submit_btn: 'Enviar',
+ confirm_code_2fa_title: 'Ingrese el código de 2FA',
+ confirm_delete_multiple_items:
+ '¿Estás seguro de que quieres eliminar permanentemente estos elementos?',
+ confirm_delete_single_item:
+ '¿Quieres eliminar este elemento permanentemente?',
+ confirm_open_apps_log_out:
+ 'Tienes aplicaciones abiertas.¿Estás seguro de que quieres cerrar sesión?',
+ confirm_new_password: 'Confirma la Nueva Contraseña',
+ confirm_delete_user:
+ '¿Estás seguro que quieres borrar esta cuenta? Todos tus archivos e información serán borrados permanentemente. Esta acción no se puede deshacer.',
+ confirm_delete_user_title: '¿Eliminar cuenta?',
+ confirm_session_revoke: '¿Estás seguro de que quieres revocar esta sesión?',
+ confirm_your_email_address: 'Confirma tu dirección de correo electrónico',
+ contact_us: 'Contáctanos',
+ contact_us_verification_required:
+ 'Debes tener un correo electrónico verificado para usar esto.',
+ contain: 'Contiene',
+ continue: 'Continuar',
+ copy: 'Copiar',
+ copy_link: 'Copiar Enlace',
+ copying: 'Copiando',
+ copying_file: 'Copiando %%',
+ cover: 'Cubrir',
+ create_account: 'Crear una cuenta',
+ create_free_account: 'Crear una cuenta gratuita',
+ create_shortcut: 'Crear un acceso directo',
+ credits: 'Creditos',
+ current_password: 'Contraseña actual',
+ cut: 'Cortar',
+ clock: 'Reloj',
+ clock_visible_hide: 'Ocultar - Siempre oculto',
+ clock_visible_show: 'Mostrar - Siempre visible',
+ clock_visible_auto:
+ 'Auto - Por defecto, visible solo en modo pantalla completa.',
+ close_all: 'Cerrar todo',
+ created: 'Creado',
+ date_modified: 'Fecha de modificación',
+ default: 'Por defecto',
+ delete: 'Borrar',
+ delete_account: 'Borrar cuenta',
+ delete_permanently: 'Borrar permanentemente',
+ deleting_file: 'Eliminando %%',
+ deploy_as_app: 'Desplegar como una aplicación',
+ descending: 'Descendiente',
+ desktop: 'Escritorio',
+ desktop_background_fit: 'Ajustar',
+ developers: 'Desarrolladores',
+ dir_published_as_website: `%strong% ha sido publicado en:`,
+ disable_2fa: 'Deshabilitar 2FA',
+ disable_2fa_confirm: '¿Estás seguro que quieres deshabilitar 2FA?',
+ disable_2fa_instructions: 'Ingresa tu contraseña para deshabilitar 2FA.',
+ disassociate_dir: 'Desvincular directorio',
+ documents: 'Documentos',
+ dont_allow: 'No permitir',
+ download: 'Descargar',
+ download_file: 'Descargar archivo',
+ downloading: 'Descargando',
+ email: 'Correo electrónico',
+ email_change_confirmation_sent:
+ 'Se ha enviado un mensaje de confirmación a tu nueva dirección de correo electrónico. Por favor, revisa tu bandeja de entrada y sigue las instrucciónes para completar el proceso.',
+ email_invalid: 'El correo electrónico no es válido.',
+ email_or_username: 'Correo electrónico o Nombre de Usuario',
+ email_required: 'El correo electrónico es obligatorio.',
+ empty_trash: 'Vaciar la papelera',
+ empty_trash_confirmation: `¿Estás seguro de que quieres borrar permanentemente todos los elementos de la Papelera?`,
+ emptying_trash: 'Vaciando la papelera…',
+ enable_2fa: 'Habilitar 2FA',
+ end_hard: 'Finalizar abruptamente',
+ end_process_force_confirm:
+ '¿Estás seguro de que quieres forzar la salida de este proceso?',
+ end_soft: 'Finalizar suavemente',
+ enlarged_qr_code: 'Código QR ampliado',
+ enter_password_to_confirm_delete_user:
+ 'Ingresa tu contraseña para confirmar la eliminación de la cuenta',
+ error_message_is_missing: 'Falta el mensaje de error.',
+ error_unknown_cause: 'Un error desconocido a ocurrido.',
+ error_uploading_files: 'Error al subir archivos',
+ favorites: 'Favoritos',
+ feedback: 'Sugerencias',
+ feedback_c2a:
+ 'Por favor, usa el formulario para enviarnos tus sugerencias, comentarios y reporte de errores.',
+ feedback_sent_confirmation:
+ 'Gracias por ponerte en contacto con nosotros. Si tienes un correo electrónico vinculado a esta cuenta, nos pondremos en contacto contigo tan pronto como podamos.',
+ fit: 'Ajustar',
+ folder: 'Carpeta',
+ force_quit: 'Forzar cierre',
+ forgot_pass_c2a: '¿Olvidaste tu contraseña?',
+ from: 'De',
+ general: 'General',
+ get_a_copy_of_on_puter: `¡Consigue una copia de '%%' en Puter.com!`,
+ get_copy_link: 'Copiar el enlace',
+ hide_all_windows: 'Ocultar todas las ventanas',
+ home: 'Inicio',
+ html_document: 'Documento HTML',
+ hue: 'Hue',
+ image: 'Imagen',
+ incorrect_password: 'Contraseña incorrecta',
+ invite_link: 'Enlace de invitación',
+ item: 'elemento',
+ items_in_trash_cannot_be_renamed: `Este elemento no se puede renombrar porque está en la papelera. Para cambiar el nombre de este archivo, primero extráelo fuera de la misma.`,
+ jpeg_image: 'Imagen JPEG',
+ keep_in_taskbar: 'Mantener en la barra de tareas',
+ language: 'Lenguage',
+ license: 'Licencia',
+ lightness: 'Claridad',
+ link_copied: 'Enlace copiado',
+ loading: 'Cargando',
+ log_in: 'Iniciar sesión',
+ log_into_another_account_anyway:
+ 'Iniciar sesión en otra cuenta de todos modos',
+ log_out: 'Cerrar sesión',
+ looks_good: 'Se ve bien!',
+ manage_sessions: 'Administrar sesión',
+ menubar_style: 'Estilo de la barra de menú',
+ menubar_style_desktop: 'Escritorio',
+ menubar_style_system: 'Sistema',
+ menubar_style_window: 'Ventana',
+ modified: 'Modified',
+ move: 'Mover',
+ moving_file: 'Moviendo %%',
+ my_websites: 'Mis páginas web',
+ name: 'Nombre',
+ name_cannot_be_empty: 'El nombre no puede estar vacío.',
+ name_cannot_contain_double_period:
+ "El nombre no puede ser el carácter '..'.",
+ name_cannot_contain_period: "El nombre no puede ser el carácter '.'.",
+ name_cannot_contain_slash: "El nombre no puede contener el carácter '/'.",
+ name_must_be_string: 'El nombre debe ser una cadena de texto.',
+ name_too_long: `El nombre no puede tener más de %% caracteres.`,
+ new: 'Nuevo',
+ new_email: 'Nuevo correo electrónico',
+ new_folder: 'Nueva carpeta',
+ new_password: 'Nueva contraseña',
+ new_username: 'Nuevo nombre de usuario',
+ no: 'No',
+ no_dir_associated_with_site:
+ 'No hay un directorio vinculado con esta dirección.',
+ no_websites_published:
+ 'Aun no has publicado ningún sitio web. Haz click derecho en una carpeta para empezar',
+ ok: 'OK',
+ open: 'Abrir',
+ open_in_new_tab: 'Abrir en una nueva pestaña',
+ open_in_new_window: 'Abrir en una nueva ventana',
+ open_with: 'Abrir con',
+ original_name: 'Nombre original',
+ original_path: 'Ruta original',
+ oss_code_and_content: 'Software y contenido de código abierto',
+ password: 'Contraseña',
+ password_changed: 'Contraseña cambiada.',
+ password_recovery_rate_limit:
+ 'Haz alcanzado nuestra tasa de refresco; por favor espera unos minutos. Para evitar esto en el futuro, evita refrescar la página muchas veces.',
+ password_recovery_token_invalid:
+ 'La contraseña de token de recuperación ya no es válida.',
+ password_recovery_unknown_error:
+ 'Ocurrió un error desconocido. Por favor, inténtalo de nuevo más tarde.',
+ password_required: 'La contraseña es obligatoria.',
+ password_strength_error:
+ 'La contraseña debe tener almenos 8 caracteres de largo y contener almenos una letra mayúscula, una minúscula, un numero, y un caracter especial.',
+ passwords_do_not_match:
+ '`Nueva Contraseña` y `Confirmar Nueva Contraseña` no coinciden.',
+ paste: 'Pegar',
+ paste_into_folder: 'Pegar en la Carpeta',
+ path: 'Ruta',
+ personalization: 'Personalización',
+ pick_name_for_website: 'Escoge un nombre para tu página web:',
+ picture: 'Imagen',
+ pictures: 'Imagenes',
+ plural_suffix: 's',
+ powered_by_puter_js: `Creado por {{link=docs}}Puter.js{{/link}}`,
+ preparing: 'Preparando...',
+ preparing_for_upload: 'Preparando para la subida...',
+ print: 'Imprimir',
+ privacy: 'Privacidad',
+ proceed_to_login: 'Procede a iniciar sesión',
+ proceed_with_account_deletion: 'Procede con la eliminación de la cuenta',
+ process_status_initializing: 'Inicializando',
+ process_status_running: 'El ejecución',
+ process_type_app: 'Aplicación',
+ process_type_init: 'Inicialización',
+ process_type_ui: 'Interfaz de usuario',
+ properties: 'Propiedades',
+ public: 'Publico',
+ publish: 'Publicar',
+ publish_as_website: 'Publicar como página web',
+ puter_description: `Puter es un servicio de nube personal enfocado en privacidad que mantiene tus archivos, aplicaciónes, y juegos en un solo lugar, accesible desde cualquier lugar en cualquier momento.`,
+ reading_file: 'Leyendo %strong%',
+ recent: 'Reciente',
+ recommended: 'Recomendado',
+ recover_password: 'Recuperar Contraseña',
+ refer_friends_c2a:
+ 'Consigue 1 GB por cada amigo que cree y confirme una cuenta en Puter ¡Tu amigo recibirá 1GB también!',
+ refer_friends_social_media_c2a: `¡Consigue 1 GB de almacenamiento gratuito en Puter.com!`,
+ refresh: 'Refrescar',
+ release_address_confirmation: `¿Estás seguro de que quieres liberar esta dirección?`,
+ remove_from_taskbar: 'Eliminar de la barra de tareas',
+ rename: 'Renombrar',
+ repeat: 'Repetir',
+ replace: 'Remplazar',
+ replace_all: 'Replace All',
+ resend_confirmation_code: 'Reenviar Código de Confirmación',
+ reset_colors: 'Restablecer colores',
+ restart_puter_confirm: '¿Estás seguro que deseas reiniciar Puter?',
+ restore: 'Restaurar',
+ save: 'Guardar',
+ saturation: 'Saturación',
+ save_account: 'Guardar cuenta',
+ save_account_to_get_copy_link: 'Por favor, crea una cuenta para continuar.',
+ save_account_to_publish: 'Por favor, crea una cuenta para continuar.',
+ save_session: 'Guardar sesión',
+ save_session_c2a:
+ 'Crea una cuenta para guardar tu sesión actual y evitar así perder tu trabajo.',
+ scan_qr_c2a:
+ 'Escanee el código a continuación para inicia sesión desde otros dispositivos',
+ scan_qr_2fa: 'Escanee el codigo QR con su aplicación de autenticación',
+ scan_qr_generic: 'Scan this QR code using your phone or another device',
+ search: 'Buscar',
+ seconds: 'segundos',
+ security: 'Seguridad',
+ select: 'Seleccionar',
+ selected: 'seleccionado',
+ select_color: 'Seleccionar color…',
+ sessions: 'Sesión',
+ send: 'Enviar',
+ send_password_recovery_email:
+ 'Enviar la contraseña al correo de recuperación',
+ session_saved: 'Gracias por crear una cuenta. La sesión ha sido guardada.',
+ set_new_password: 'Establecer una nueva contraseña',
+ settings: 'Opciones',
+ share: 'Compartir',
+ share_to: 'Compartir a',
+ share_with: 'Compartir con:',
+ shortcut_to: 'Acceso directo a',
+ show_all_windows: 'Mostrar todas las ventanas',
+ show_hidden: 'Mostrar ocultos',
+ sign_in_with_puter: 'Inicia sesión con Puter',
+ sign_up: 'Registrarse',
+ signing_in: 'Registrándose…',
+ size: 'Tamaño',
+ skip: 'Saltar',
+ something_went_wrong: 'Algo salió mal.',
+ sort_by: 'Ordenar Por',
+ start: 'Inicio',
+ status: 'Estado',
+ storage_usage: 'Uso del almacenamiento',
+ storage_puter_used: 'Usado por Puter',
+ taking_longer_than_usual:
+ 'Tardando un poco más de lo habitual. Por favor, espere...',
+ task_manager: 'Administrador de tareas',
+ taskmgr_header_name: 'Nombre',
+ taskmgr_header_status: 'Estado',
+ taskmgr_header_type: 'Tipo',
+ terms: 'Terminos',
+ text_document: 'Documento de Texto',
+ tos_fineprint: `Al hacer clic en 'Crear una cuenta gratuita' aceptas los {{link=terms}}términos del servicio{{/link}} y {{link=privacy}}la política de privacidad{{/link}} de Puter.`,
+ transparency: 'Transparencia',
+ trash: 'Papelera',
+ two_factor: 'Autenticación de dos factores',
+ two_factor_disabled: '2FA Deshabilitadp',
+ two_factor_enabled: '2FA Habilitado',
+ type: 'Tipo',
+ type_confirm_to_delete_account:
+ "Ingrese 'Confirmar' para borrar esta cuenta.",
+ ui_colors: 'Colores de interfaz',
+ ui_manage_sessions: 'Administrador de sesión',
+ ui_revoke: 'Revocar',
+ undo: 'Deshacer',
+ unlimited: 'Ilimitado',
+ unzip: 'Descomprimir',
+ upload: 'Subir',
+ upload_here: 'Subir aquí',
+ usage: 'Uso',
+ username: 'Nombre de usuario',
+ username_changed: 'Nombre de usuario actualizado correctamente.',
+ username_required: 'El nombre de usuario es obligatorio.',
+ versions: 'Versiones',
+ videos: 'Videos',
+ visibility: 'Visibilidad',
+ yes: 'Si',
+ yes_release_it: 'Sí, libéralo',
+ you_have_been_referred_to_puter_by_a_friend:
+ '¡Has sido invitado a Puter por un amigo!',
+ zip: 'Zip',
+ zipping_file: 'Compriminendo %strong%',
- // === 2FA Setup ===
- setup2fa_1_step_heading: 'Abre tu aplicación de autenticación',
- setup2fa_1_instructions: `
+ // === 2FA Setup ===
+ setup2fa_1_step_heading: 'Abre tu aplicación de autenticación',
+ setup2fa_1_instructions: `
Puedes usar cualquier aplicación de autenticación que soporte el protocolo de Time-based One-time (TOTP).
Hay muchos para elegir, pero si no estas seguro
Authy
es una opción segura para Android y iOS.
`,
- setup2fa_2_step_heading: 'Escanea el código QR',
- setup2fa_3_step_heading: 'Ingresa el código de 6 dígitos',
- setup2fa_4_step_heading: 'Copiar tus códigos de recuperación',
- setup2fa_4_instructions: `
+ setup2fa_2_step_heading: 'Escanea el código QR',
+ setup2fa_3_step_heading: 'Ingresa el código de 6 dígitos',
+ setup2fa_4_step_heading: 'Copiar tus códigos de recuperación',
+ setup2fa_4_instructions: `
Estos códigos de recuperación son la única forma de acceder a tu cuenta, si pierdes tu teléfono o no puedes usar la aplicación de autenticación.
Asegurate de guardarlos en un lugar seguro.
`,
- setup2fa_5_step_heading: 'Confirmar la configuración de 2FA',
- setup2fa_5_confirmation_1: 'He guardado mis códigos de recuperación en un lugar seguro',
- setup2fa_5_confirmation_2: 'Estoy listo para habilitar 2FA',
- setup2fa_5_button: 'Habilitar 2FA',
+ setup2fa_5_step_heading: 'Confirmar la configuración de 2FA',
+ setup2fa_5_confirmation_1:
+ 'He guardado mis códigos de recuperación en un lugar seguro',
+ setup2fa_5_confirmation_2: 'Estoy listo para habilitar 2FA',
+ setup2fa_5_button: 'Habilitar 2FA',
+
+ // === 2FA Login ===
+ login2fa_otp_title: 'Ingresar el código 2FA',
+ login2fa_otp_instructions:
+ 'Ingresa tu código de 6 dígitos de tu aplicación de autenticación.',
+ login2fa_recovery_title: 'Ingresa tu código de recuperación',
+ login2fa_recovery_instructions:
+ 'Ingresa uno de tus códigos de recuperación para acceder a tu cuenta.',
+ login2fa_use_recovery_code: 'Usar un código de recuperación',
+ login2fa_recovery_back: 'Atras',
+ login2fa_recovery_placeholder: 'XXXXXXXX',
+
+ change: 'cambiar', // In English: "Change"
+ clock_visibility: 'visibilidadReloj', // In English: "Clock Visibility"
+ reading: 'lectura %strong%', // In English: "Reading %strong%"
+ writing: 'escribiendo %strong%', // In English: "Writing %strong%"
+ unzipping: 'descomprimiendo %strong%', // In English: "Unzipping %strong%"
+ sequencing: 'secuenciación %strong%', // In English: "Sequencing %strong%"
+ zipping: 'comprimiendo %strong%', // In English: "Zipping %strong%"
+ Editor: 'Editor', // In English: "Editor"
+ Viewer: 'Espectador', // In English: "Viewer"
+ 'People with access': 'Personas con acceso', // In English: "People with access"
+ 'Share With…': 'Compartir con…', // In English: "Share With…"
+ Owner: 'Propietario', // In English: "Owner"
+ "You can't share with yourself.": 'No puedes compartir contigo mismo.', // In English: "You can't share with yourself."
+ 'This user already has access to this item':
+ 'Este usuario ya tiene acceso a este elemento.', // In English: "This user already has access to this item"
- // === 2FA Login ===
- login2fa_otp_title: 'Ingresar el código 2FA',
- login2fa_otp_instructions: 'Ingresa tu código de 6 dígitos de tu aplicación de autenticación.',
- login2fa_recovery_title: 'Ingresa tu código de recuperación',
- login2fa_recovery_instructions: 'Ingresa uno de tus códigos de recuperación para acceder a tu cuenta.',
- login2fa_use_recovery_code: 'Usar un código de recuperación',
- login2fa_recovery_back: 'Atras',
- login2fa_recovery_placeholder: 'XXXXXXXX',
+ // === Billing ===
- "change": "cambiar", // In English: "Change"
- "clock_visibility": "visibilidadReloj", // In English: "Clock Visibility"
- "reading": "lectura %strong%", // In English: "Reading %strong%"
- "writing": "escribiendo %strong%", // In English: "Writing %strong%"
- "unzipping": "descomprimiendo %strong%", // In English: "Unzipping %strong%"
- "sequencing": "secuenciación %strong%", // In English: "Sequencing %strong%"
- "zipping": "comprimiendo %strong%", // In English: "Zipping %strong%"
- "Editor": "Editor", // In English: "Editor"
- "Viewer": "Espectador", // In English: "Viewer"
- "People with access": "Personas con acceso", // In English: "People with access"
- "Share With…": "Compartir con…", // In English: "Share With…"
- "Owner": "Propietario", // In English: "Owner"
- "You can't share with yourself.": "No puedes compartir contigo mismo.", // In English: "You can't share with yourself."
- "This user already has access to this item": "Este usuario ya tiene acceso a este elemento." // In English: "This user already has access to this item"
-
- }
-};
+ 'billing.change_payment_method': 'Cambiar método de pago', // In English: "Change Payment Method"
+ 'billing.cancel': 'Cancelar', // In English: "Cancel"
+ 'billing.download_invoice': 'Descargar factura', // In English: "Download Invoice"
+ 'billing.payment_method': 'Método de pago', // In English: "Payment Method"
+ 'billing.payment_method_updated': '¡Método de pago actualizado!', // In English: "Payment method updated!"
+ 'billing.confirm_payment_method': 'Confirmar método de pago', // In English: "Confirm Payment Method"
+ 'billing.payment_history': 'Historial de pagos', // In English: "Payment History"
+ 'billing.refunded': 'Reembolsado', // In English: "Refunded"
+ 'billing.paid': 'Pagado', // In English: "Paid"
+ 'billing.ok': 'Aceptar', // In English: "OK"
+ 'billing.resume_subscription': 'Reanudar suscripción', // In English: "Resume Subscription"
+ 'billing.subscription_cancelled': 'Tu suscripción ha sido cancelada.', // In English: "Your subscription has been canceled."
+ 'billing.subscription_cancelled_description':
+ 'Aún tendrás acceso a tu suscripción hasta el final de este periodo de facturación.', // 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': 'Profesional', // In English: "Professional"
+ 'billing.offering.business': 'Negocios', // In English: "Business"
+ 'billing.cloud_storage': 'Almacenamiento en la nube', // In English: "Cloud Storage"
+ 'billing.ai_access': 'Acceso a IA', // In English: "AI Access"
+ 'billing.bandwidth': 'Ancho de banda', // In English: "Bandwidth"
+ 'billing.apps_and_games': 'Aplicaciones y juegos', // In English: "Apps & Games"
+ 'billing.upgrade_to_pro': 'Actualizar a %strong%', // In English: "Upgrade to %strong%"
+ 'billing.switch_to': 'Cambiar a %strong%', // In English: "Switch to %strong%"
+ 'billing.payment_setup': 'Configuración de pago', // In English: "Payment Setup"
+ 'billing.back': 'Atrás', // In English: "Back"
+ 'billing.you_are_now_subscribed_to':
+ 'Ahora estás suscrito al nivel %strong%.', // In English: "You are now subscribed to %strong% tier."
+ 'billing.you_are_now_subscribed_to_without_tier': 'Ahora estás suscrito', // In English: "You are now subscribed"
+ 'billing.subscription_cancellation_confirmation':
+ '¿Estás seguro de que deseas cancelar tu suscripción?', // In English: "Are you sure you want to cancel your subscription?"
+ 'billing.subscription_setup': 'Configuración de suscripción', // In English: "Subscription Setup"
+ 'billing.cancel_it': 'Cancelar', // In English: "Cancel It"
+ 'billing.keep_it': 'Mantenerlo', // In English: "Keep It"
+ 'billing.subscription_resumed':
+ '¡Tu suscripción %strong% ha sido reanudada!', // In English: "Your %strong% subscription has been resumed!"
+ 'billing.upgrade_now': 'Actualizar ahora', // In English: "Upgrade Now"
+ 'billing.upgrade': 'Actualizar', // In English: "Upgrade"
+ 'billing.currently_on_free_plan': 'Actualmente estás en el plan gratuito.', // In English: "You are currently on the free plan."
+ 'billing.download_receipt': 'Descargar recibo', // In English: "Download Receipt"
+ 'billing.subscription_check_error':
+ 'Ocurrió un problema al verificar el estado de tu suscripción.', // In English: "A problem occurred while checking your subscription status."
+ 'billing.email_confirmation_needed':
+ 'Tu correo electrónico no ha sido confirmado. Te enviaremos un código para confirmarlo ahora.', // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ 'billing.sub_cancelled_but_valid_until':
+ 'Has cancelado tu suscripción y se cambiará automáticamente al nivel gratuito al final del periodo de facturación. No se te cobrará nuevamente a menos que te vuelvas a suscribir.', // 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':
+ 'Tu plan actual hasta el final de este periodo de facturación.', // In English: "Your current plan until the end of this billing period."
+ 'billing.current_plan': 'Plan actual', // In English: "Current plan"
+ 'billing.cancelled_subscription_tier': 'Suscripción cancelada (%%)', // In English: "Cancelled Subscription (%%)"
+ 'billing.manage': 'Gestionar', // In English: "Manage"
+ 'billing.limited': 'Limitado', // In English: "Limited"
+ 'billing.expanded': 'Expandido', // In English: "Expanded"
+ 'billing.accelerated': 'Acelerado', // In English: "Accelerated"
+ 'billing.enjoy_msg':
+ 'Disfruta %% de almacenamiento en la nube junto con otros beneficios.', // In English: "Enjoy %% of Cloud Storage plus other benefits."
+ },
+}
-export default es;
+export default es
diff --git a/src/gui/src/i18n/translations/fa.js b/src/gui/src/i18n/translations/fa.js
index 76d21f2ce2..eb66665443 100644
--- a/src/gui/src/i18n/translations/fa.js
+++ b/src/gui/src/i18n/translations/fa.js
@@ -392,6 +392,54 @@ const fa = {
"شما نمیتوانید با خودتان به اشتراک بگذارید", // In English: "You can't share with yourself."
"This user already has access to this item":
"این کاربر از قبل به این مورد دسترسی دارد", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "تغییر روش پرداخت", // In English: "Change"
+ "billing.cancel": "لغو", // In English: "Cancel"
+ "billing.download_invoice": "دانلود فاکتور", // In English: "Download"
+ "billing.payment_method": "روش پرداخت", // In English: "Payment Method"
+ "billing.payment_method_updated": "روش پرداخت بهروزرسانی شد!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "تأیید روش پرداخت", // In English: "Confirm Payment Method"
+ "billing.payment_history": "تاریخچه پرداخت", // In English: "Payment History"
+ "billing.refunded": "بازپرداخت شده", // In English: "Refunded"
+ "billing.paid": "پرداخت شده", // In English: "Paid"
+ "billing.ok": "تأیید", // In English: "OK"
+ "billing.resume_subscription": "از سرگیری اشتراک", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "اشتراک شما لغو شده است.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "شما تا پایان این دوره صورتحساب همچنان به اشتراک خود دسترسی خواهید داشت.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "رایگان", // In English: "Free"
+ "billing.offering.pro": "حرفهای", // In English: "Professional"
+ "billing.offering.business": "تجاری", // In English: "Business"
+ "billing.cloud_storage": "فضای ذخیرهسازی ابری", // In English: "Cloud Storage"
+ "billing.ai_access": "دسترسی به هوش مصنوعی", // In English: "AI Access"
+ "billing.bandwidth": "پهنای باند", // In English: "Bandwidth"
+ "billing.apps_and_games": "برنامهها و بازیها", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "ارتقا به %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "تغییر به %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "تنظیم پرداخت", // In English: "Payment Setup"
+ "billing.back": "بازگشت", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "شما اکنون مشترک سطح %strong% هستید.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "شما اکنون مشترک شدهاید", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "آیا مطمئن هستید که میخواهید اشتراک خود را لغو کنید؟", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "تنظیم اشتراک", // In English: "Subscription Setup"
+ "billing.cancel_it": "لغو کن", // In English: "Cancel It"
+ "billing.keep_it": "نگه دار", // In English: "Keep It"
+ "billing.subscription_resumed": "اشتراک %strong% شما از سر گرفته شد!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "هماکنون ارتقا دهید", // In English: "Upgrade Now"
+ "billing.upgrade": "ارتقا", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "شما در حال حاضر در طرح رایگان هستید.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "دانلود رسید", // In English: "Download Receipt"
+ "billing.subscription_check_error": "هنگام بررسی وضعیت اشتراک شما مشکلی پیش آمد.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "ایمیل شما تأیید نشده است. ما اکنون یک کد برای تأیید آن ارسال خواهیم کرد.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "شما اشتراک خود را لغو کردهاید و در پایان دوره صورتحساب به طور خودکار به سطح رایگان تغییر خواهد کرد. تا زمانی که مجدداً مشترک نشوید، هزینهای از شما دریافت نخواهد شد.", // 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": "طرح فعلی شما تا پایان این دوره صورتحساب.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "طرح فعلی", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "اشتراک لغو شده (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "مدیریت", // In English: "Manage"
+ "billing.limited": "محدود", // In English: "Limited"
+ "billing.expanded": "گسترش یافته", // In English: "Expanded"
+ "billing.accelerated": "تسریع شده", // In English: "Accelerated"
+ "billing.enjoy_msg": "از %% فضای ذخیرهسازی ابری به همراه سایر مزایا لذت ببرید." // In English: "Enjoy %% of Cloud Storage plus other benefits."
+
},
};
diff --git a/src/gui/src/i18n/translations/fi.js b/src/gui/src/i18n/translations/fi.js
index c9d0d4e814..7c5bdd3b61 100644
--- a/src/gui/src/i18n/translations/fi.js
+++ b/src/gui/src/i18n/translations/fi.js
@@ -17,412 +17,491 @@
* along with this program. If not, see .
*/
-
const fi = {
- name: "Suomi",
- english_name: "Finnish",
- code: "fi",
- dictionary: {
- about: "Tietoa",
- account: "Tili",
- account_password: "Vahvista tilin salasana",
- access_granted_to: "Käyttöoikeus myönnetty",
- add_existing_account: "Kirjaudu olemassaolevalla tilillä",
- all_fields_required: 'Kaikki kentät on täytettävä.',
- allow: 'Salli',
- apply: "Käytä", // TODO: Ambiguous meaning
- // To apply(a principle) => "Sovella" or
- // Apply for(a job) "Hae" or
- // Apply as(an engineer) => "Hakeudu" or
- // Apply an expression => "Applikoi" or - Probably the most appropriate in the context of the app
- // Apply in the sense of applying something, like a tool => "Käytä"
+ name: "Suomi",
+ english_name: "Finnish",
+ code: "fi",
+ dictionary: {
+ about: "Tietoa",
+ account: "Tili",
+ account_password: "Vahvista tilin salasana",
+ access_granted_to: "Käyttöoikeus myönnetty",
+ add_existing_account: "Kirjaudu olemassaolevalla tilillä",
+ all_fields_required: "Kaikki kentät on täytettävä.",
+ allow: "Salli",
+ apply: "Käytä", // TODO: Ambiguous meaning
+ // To apply(a principle) => "Sovella" or
+ // Apply for(a job) "Hae" or
+ // Apply as(an engineer) => "Hakeudu" or
+ // Apply an expression => "Applikoi" or - Probably the most appropriate in the context of the app
+ // Apply in the sense of applying something, like a tool => "Käytä"
- ascending: 'Nouseva',
- associated_websites: "Tähän liittyvät verkkosivustot",
- auto_arrange: 'Järjestä automaattisesti',
- background: "Tausta",
- browse: "Selaa",
- cancel: 'Peruuta',
- center: 'Keskitä',
- change_desktop_background: 'Vaihda työpöydän taustakuvaa…',
- change_email: "Vaihda sähköpostiosoite",
- change_language: "Vaihda kieli",
- change_password: "Vaihda salasana",
- change_ui_colors: "Vaihda käyttöliittymän värejä",
- change_username: "Vaihda käyttäjänimi",
- close: 'Sulje',
- close_all_windows: "Sulje kaikki ikkunat",
- close_all_windows_confirm: "Haluatko varmasti sulkea kaikki ikkunat?",
- close_all_windows_and_log_out: 'Sulje ikkunat ja kirjaudu ulos',
- change_always_open_with: "Haluatko aina avata tämän tyyppisen tiedoston sovelluksella",
- color: 'Väri',
- confirm: 'Vahvista',
- confirm_2fa_setup: 'Olen lisännyt koodin todennussovellukseeni',
- confirm_2fa_recovery: 'Olen tallentanut palautuskoodini turvalliseen paikkaan',
- confirm_account_for_free_referral_storage_c2a: 'Luo tili ja vahvista sähköpostiosoitteesi saadaksesi 1 Gt ilmaista tallennustilaa. Myös kaverisi saa 1 Gt:n ilmaista tallennustilaa.',
- confirm_code_generic_incorrect: "Väärä koodi.",
- confirm_code_generic_too_many_requests: "Liikaa pyyntöjä. Ole hyvä ja odota muutama minuutti.",
- confirm_code_generic_submit: "Lähetä koodi",
- confirm_code_generic_try_again: "Yritä uudelleen",
- confirm_code_generic_title: "Syötä vahvistuskoodi",
- confirm_code_2fa_instruction: "Syötä kuusinumeroinen koodi todennussovelluksestasi.",
- confirm_code_2fa_submit_btn: "Lähetä",
- confirm_code_2fa_title: "Syötä kaksivaiheisen tunnistautumisen koodi",
- confirm_delete_multiple_items: 'Haluatko varmasti poistaa nämä kohteet pysyvästi?',
- confirm_delete_single_item: 'Haluatko poistaa tämän kohteen pysyvästi?',
- confirm_open_apps_log_out: 'Sinulla on avoimia sovelluksia. Haluatko varmasti kirjautua ulos?',
- confirm_new_password: "Vahvista uusi salasana",
- confirm_delete_user: "Haluatko varmasti poistaa tilisi? Kaikki tiedostosi ja tietosi poistetaan pysyvästi. Tätä toimintoa ei voi kumota.",
- confirm_delete_user_title: "Poista tilisi?",
- confirm_session_revoke: "Haluatko varmasti peruuttaa tämän istunnon?",
- confirm_your_email_address: "Vahvista sähköpostiosoitteesi",
- contact_us: "Ota yhteyttä",
- contact_us_verification_required: "Sinulla on oltava vahvistettu sähköpostiosoite, jotta voit käyttää tätä.",
- contain: 'Sisällytä', // TODO: Ambiguous meaning
- // "inside(a house)" => "Sisällä" - probably more appropriate
- // "contain within" => "Sisältää"
+ ascending: "Nouseva",
+ associated_websites: "Tähän liittyvät verkkosivustot",
+ auto_arrange: "Järjestä automaattisesti",
+ background: "Tausta",
+ browse: "Selaa",
+ cancel: "Peruuta",
+ center: "Keskitä",
+ change_desktop_background: "Vaihda työpöydän taustakuvaa…",
+ change_email: "Vaihda sähköpostiosoite",
+ change_language: "Vaihda kieli",
+ change_password: "Vaihda salasana",
+ change_ui_colors: "Vaihda käyttöliittymän värejä",
+ change_username: "Vaihda käyttäjänimi",
+ close: "Sulje",
+ close_all_windows: "Sulje kaikki ikkunat",
+ close_all_windows_confirm: "Haluatko varmasti sulkea kaikki ikkunat?",
+ close_all_windows_and_log_out: "Sulje ikkunat ja kirjaudu ulos",
+ change_always_open_with:
+ "Haluatko aina avata tämän tyyppisen tiedoston sovelluksella",
+ color: "Väri",
+ confirm: "Vahvista",
+ confirm_2fa_setup: "Olen lisännyt koodin todennussovellukseeni",
+ confirm_2fa_recovery:
+ "Olen tallentanut palautuskoodini turvalliseen paikkaan",
+ confirm_account_for_free_referral_storage_c2a:
+ "Luo tili ja vahvista sähköpostiosoitteesi saadaksesi 1 Gt ilmaista tallennustilaa. Myös kaverisi saa 1 Gt:n ilmaista tallennustilaa.",
+ confirm_code_generic_incorrect: "Väärä koodi.",
+ confirm_code_generic_too_many_requests:
+ "Liikaa pyyntöjä. Ole hyvä ja odota muutama minuutti.",
+ confirm_code_generic_submit: "Lähetä koodi",
+ confirm_code_generic_try_again: "Yritä uudelleen",
+ confirm_code_generic_title: "Syötä vahvistuskoodi",
+ confirm_code_2fa_instruction:
+ "Syötä kuusinumeroinen koodi todennussovelluksestasi.",
+ confirm_code_2fa_submit_btn: "Lähetä",
+ confirm_code_2fa_title: "Syötä kaksivaiheisen tunnistautumisen koodi",
+ confirm_delete_multiple_items:
+ "Haluatko varmasti poistaa nämä kohteet pysyvästi?",
+ confirm_delete_single_item: "Haluatko poistaa tämän kohteen pysyvästi?",
+ confirm_open_apps_log_out:
+ "Sinulla on avoimia sovelluksia. Haluatko varmasti kirjautua ulos?",
+ confirm_new_password: "Vahvista uusi salasana",
+ confirm_delete_user:
+ "Haluatko varmasti poistaa tilisi? Kaikki tiedostosi ja tietosi poistetaan pysyvästi. Tätä toimintoa ei voi kumota.",
+ confirm_delete_user_title: "Poista tilisi?",
+ confirm_session_revoke: "Haluatko varmasti peruuttaa tämän istunnon?",
+ confirm_your_email_address: "Vahvista sähköpostiosoitteesi",
+ contact_us: "Ota yhteyttä",
+ contact_us_verification_required:
+ "Sinulla on oltava vahvistettu sähköpostiosoite, jotta voit käyttää tätä.",
+ contain: "Sisällytä", // TODO: Ambiguous meaning
+ // "inside(a house)" => "Sisällä" - probably more appropriate
+ // "contain within" => "Sisältää"
- continue: "Jatka",
+ continue: "Jatka",
- copy: 'Kopioi', // TODO: Lexical categories
- // Noun "A copy of something" => 'Kopio' or
- // Verb "To copy something" => 'Kopioi'?
+ copy: "Kopioi", // TODO: Lexical categories
+ // Noun "A copy of something" => 'Kopio' or
+ // Verb "To copy something" => 'Kopioi'?
- copy_link: "Kopioi linkki",
- copying: "Kopioidaan",
- copying_file: "Kopioidaan %%",
- cover: 'Kansi', // TODO: Lexical categories
- // Noun (shelter) => 'Suoja' or
- // Noun (lid) => 'Kansi' or
- // Intransitive Verb (To occlude something) => 'Peitä' or
- // Transitive Verb (To cover for someone) => 'Suojaa'
+ copy_link: "Kopioi linkki",
+ copying: "Kopioidaan",
+ copying_file: "Kopioidaan %%",
+ cover: "Kansi", // TODO: Lexical categories
+ // Noun (shelter) => 'Suoja' or
+ // Noun (lid) => 'Kansi' or
+ // Intransitive Verb (To occlude something) => 'Peitä' or
+ // Transitive Verb (To cover for someone) => 'Suojaa'
- create_account: "Luo tili",
- create_free_account: "Luo ilmainen tili",
- create_shortcut: "Luo pikakuvake",
- credits: "Tekijät",
- current_password: "Nykyinen salasana",
- cut: 'Leikkaa',
- clock: "Kello",
- clock_visible_hide: 'Piilota - aina piilossa',
- clock_visible_show: 'Näytä - aina näkyvissä',
- clock_visible_auto: 'Automaattinen - oletus, näkyy vain koko näytön tilassa.',
- close_all: 'Sulje kaikki',
- created: 'Luotu',
- date_modified: 'Muokkauspäivämäärä',
- default: 'Oletus',
- delete: 'Poista',
- delete_account: "Poista tilisi",
- delete_permanently: "Poista pysyvästi",
- deleting_file: "Poistetaan %%",
- deploy_as_app: 'Ota käyttöön sovelluksena',
- descending: 'Laskeva',
- desktop: 'Työpöytä',
- desktop_background_fit: "Sovita",
- developers: "Kehittäjät",
- dir_published_as_website: `%strong% on julkaistu osoitteessa:`,
- disable_2fa: 'Ota kaksivaiheinen tunnistautuminen pois käytöstä',
- disable_2fa_confirm: "Haluatko varmasti poistaa kaksivaiheisen tunnistautumisen käytöstä?",
- disable_2fa_instructions: "Syötä salasanasi poistaaksesi kaksivaihesen tunnistautumisen käytöstä.",
- disassociate_dir: "Irrota hakemisto",
- documents: 'Dokumentit',
- dont_allow: 'Älä salli',
- download: 'Lataa',
- download_file: 'Lataa tiedosto',
- downloading: "Ladataan",
- email: "Sähköpostiosoite",
- email_change_confirmation_sent: "Vahvistusviesti on lähetetty uuteen sähköpostiosoitteeseesi. Tarkista postilaatikkosi ja viimeistele prosessi seuraamalla ohjeita.",
- email_invalid: 'Sähköpostiosoite on virheellinen.',
- email_or_username: "Sähköposti tai Käyttäjänimi",
- email_required: 'Sähköpostiosoite vaaditaan.',
- empty_trash: 'Tyhjennä roskakori',
- empty_trash_confirmation: `Haluatko varmasti poistaa roskakorissa olevat kohteet pysyvästi?`,
- emptying_trash: 'Tyhjennetään roskakoria...',
- enable_2fa: 'Ota käyttöön kaksivaiheinen tunnistautuminen',
- end_hard: "Pakotettu lopetus",
- end_process_force_confirm: "Haluatko varmasti pakottaa prosessin lopetuksen?",
- end_soft: "Pehmeä lopetus",
- enlarged_qr_code: "Suurennettu QR-koodi",
- enter_password_to_confirm_delete_user: "Syötä salasanasi vahvistaaksesi tilisi poiston",
- error_message_is_missing: "Virheilmoitus puuttuu.",
- error_unknown_cause: "Tuntematon virhe.",
- error_uploading_files: "Tiedostojen lataaminen epäonnistui",
- favorites: "Suosikit",
- feedback: "Palaute",
- feedback_c2a: "Käytä alla olevaa lomaketta lähettääksesi meille palautetta, kommentteja ja vikailmoituksia.",
- feedback_sent_confirmation: "Kiitos yhteydenotostasi. Jos tiliisi on liitetty sähköpostiosoite, saat meiltä vastauksen mahdollisimman pian.",
- fit: "Sovita",
- folder: 'Kansio',
- force_quit: 'Pakota lopetus',
- forgot_pass_c2a: "Unohditko salasanasi?",
+ create_account: "Luo tili",
+ create_free_account: "Luo ilmainen tili",
+ create_shortcut: "Luo pikakuvake",
+ credits: "Tekijät",
+ current_password: "Nykyinen salasana",
+ cut: "Leikkaa",
+ clock: "Kello",
+ clock_visible_hide: "Piilota - aina piilossa",
+ clock_visible_show: "Näytä - aina näkyvissä",
+ clock_visible_auto:
+ "Automaattinen - oletus, näkyy vain koko näytön tilassa.",
+ close_all: "Sulje kaikki",
+ created: "Luotu",
+ date_modified: "Muokkauspäivämäärä",
+ default: "Oletus",
+ delete: "Poista",
+ delete_account: "Poista tilisi",
+ delete_permanently: "Poista pysyvästi",
+ deleting_file: "Poistetaan %%",
+ deploy_as_app: "Ota käyttöön sovelluksena",
+ descending: "Laskeva",
+ desktop: "Työpöytä",
+ desktop_background_fit: "Sovita",
+ developers: "Kehittäjät",
+ dir_published_as_website: `%strong% on julkaistu osoitteessa:`,
+ disable_2fa: "Ota kaksivaiheinen tunnistautuminen pois käytöstä",
+ disable_2fa_confirm:
+ "Haluatko varmasti poistaa kaksivaiheisen tunnistautumisen käytöstä?",
+ disable_2fa_instructions:
+ "Syötä salasanasi poistaaksesi kaksivaihesen tunnistautumisen käytöstä.",
+ disassociate_dir: "Irrota hakemisto",
+ documents: "Dokumentit",
+ dont_allow: "Älä salli",
+ download: "Lataa",
+ download_file: "Lataa tiedosto",
+ downloading: "Ladataan",
+ email: "Sähköpostiosoite",
+ email_change_confirmation_sent:
+ "Vahvistusviesti on lähetetty uuteen sähköpostiosoitteeseesi. Tarkista postilaatikkosi ja viimeistele prosessi seuraamalla ohjeita.",
+ email_invalid: "Sähköpostiosoite on virheellinen.",
+ email_or_username: "Sähköposti tai Käyttäjänimi",
+ email_required: "Sähköpostiosoite vaaditaan.",
+ empty_trash: "Tyhjennä roskakori",
+ empty_trash_confirmation: `Haluatko varmasti poistaa roskakorissa olevat kohteet pysyvästi?`,
+ emptying_trash: "Tyhjennetään roskakoria...",
+ enable_2fa: "Ota käyttöön kaksivaiheinen tunnistautuminen",
+ end_hard: "Pakotettu lopetus",
+ end_process_force_confirm:
+ "Haluatko varmasti pakottaa prosessin lopetuksen?",
+ end_soft: "Pehmeä lopetus",
+ enlarged_qr_code: "Suurennettu QR-koodi",
+ enter_password_to_confirm_delete_user:
+ "Syötä salasanasi vahvistaaksesi tilisi poiston",
+ error_message_is_missing: "Virheilmoitus puuttuu.",
+ error_unknown_cause: "Tuntematon virhe.",
+ error_uploading_files: "Tiedostojen lataaminen epäonnistui",
+ favorites: "Suosikit",
+ feedback: "Palaute",
+ feedback_c2a:
+ "Käytä alla olevaa lomaketta lähettääksesi meille palautetta, kommentteja ja vikailmoituksia.",
+ feedback_sent_confirmation:
+ "Kiitos yhteydenotostasi. Jos tiliisi on liitetty sähköpostiosoite, saat meiltä vastauksen mahdollisimman pian.",
+ fit: "Sovita",
+ folder: "Kansio",
+ force_quit: "Pakota lopetus",
+ forgot_pass_c2a: "Unohditko salasanasi?",
- from: "Henkilöltä", // TODO: Context dependent, examples
- // "from address" => "osoitteesta" or
- // "from sender" => "lähettäjältä".
- // In the finnish language these are usually translated as case suffixes.
- // "From Person" gets the suffix "-ltä", being the combination of "Henkilö(Person) and ltä(From)"
+ from: "Henkilöltä", // TODO: Context dependent, examples
+ // "from address" => "osoitteesta" or
+ // "from sender" => "lähettäjältä".
+ // In the finnish language these are usually translated as case suffixes.
+ // "From Person" gets the suffix "-ltä", being the combination of "Henkilö(Person) and ltä(From)"
- general: "Yleinen", // TODO: Conceptual ambiguity
- // "general (about something)" => "Yleistä" or
- // "military general" => "Kenraali"
+ general: "Yleinen", // TODO: Conceptual ambiguity
+ // "general (about something)" => "Yleistä" or
+ // "military general" => "Kenraali"
- get_a_copy_of_on_puter: `Hanki '%%' -kopio Puter.com-sivustolta!`, // TODO: Very difficult ambiguity due to different case suffix for any possible word that you can substitue here. Can stay as is, but it's not exactly correct.
+ get_a_copy_of_on_puter: `Hanki '%%' -kopio Puter.com-sivustolta!`, // TODO: Very difficult ambiguity due to different case suffix for any possible word that you can substitue here. Can stay as is, but it's not exactly correct.
- get_copy_link: 'Hanki kopiolinkki', // TODO: Ambiguous meaning
- // 'get a copy of a link' => 'Ota Kopio Linkkiin' or
- // 'get a link to the copy' => 'Ota Linkki Kopioon' - More probable, just want to be sure
+ get_copy_link: "Hanki kopiolinkki", // TODO: Ambiguous meaning
+ // 'get a copy of a link' => 'Ota Kopio Linkkiin' or
+ // 'get a link to the copy' => 'Ota Linkki Kopioon' - More probable, just want to be sure
- hide_all_windows: "Piilota kaikki ikkunat",
- home: 'Koti',
- html_document: 'HTML-dokumentti',
- hue: 'Sävy',
- image: 'Kuva',
- incorrect_password: "Väärä salasana",
- invite_link: "Kutsulinkki",
- item: 'kohde',
- items_in_trash_cannot_be_renamed: `Tätä kohdetta ei voi nimetä uudelleen, koska se on roskakorissa. Jos haluat nimetä kohteen uudelleen, palauta se ensin roskakorista.`,
- jpeg_image: 'JPEG-kuva',
- keep_in_taskbar: 'Pidä tehtäväpalkissa',
- language: "Kieli",
- license: "Lisenssi",
- lightness: 'Valoisuus',
- link_copied: "Linkki kopioitu",
- loading: 'Ladataan',
- log_in: "Kirjaudu Sisään",
- log_into_another_account_anyway: 'Kirjaudu joka tapauksessa toiselle tilille',
- log_out: 'Kirjaudu ulos',
- looks_good: "Näyttää hyvältä!",
- manage_sessions: "Hallitse istuntoja",
- menubar_style: "Valikkopalkin tyyli",
- menubar_style_desktop: "Työpöytä",
- menubar_style_system: "Järjestelmä",
- menubar_style_window: "Ikkuna",
- modified: 'Muokattu',
- move: 'Siirrä',
- moving_file: "Siirretään %%",
- my_websites: "Sivustoni",
- name: 'Nimi',
- name_cannot_be_empty: 'Nimi ei voi olla tyhjä.',
+ hide_all_windows: "Piilota kaikki ikkunat",
+ home: "Koti",
+ html_document: "HTML-dokumentti",
+ hue: "Sävy",
+ image: "Kuva",
+ incorrect_password: "Väärä salasana",
+ invite_link: "Kutsulinkki",
+ item: "kohde",
+ items_in_trash_cannot_be_renamed: `Tätä kohdetta ei voi nimetä uudelleen, koska se on roskakorissa. Jos haluat nimetä kohteen uudelleen, palauta se ensin roskakorista.`,
+ jpeg_image: "JPEG-kuva",
+ keep_in_taskbar: "Pidä tehtäväpalkissa",
+ language: "Kieli",
+ license: "Lisenssi",
+ lightness: "Valoisuus",
+ link_copied: "Linkki kopioitu",
+ loading: "Ladataan",
+ log_in: "Kirjaudu Sisään",
+ log_into_another_account_anyway:
+ "Kirjaudu joka tapauksessa toiselle tilille",
+ log_out: "Kirjaudu ulos",
+ looks_good: "Näyttää hyvältä!",
+ manage_sessions: "Hallitse istuntoja",
+ menubar_style: "Valikkopalkin tyyli",
+ menubar_style_desktop: "Työpöytä",
+ menubar_style_system: "Järjestelmä",
+ menubar_style_window: "Ikkuna",
+ modified: "Muokattu",
+ move: "Siirrä",
+ moving_file: "Siirretään %%",
+ my_websites: "Sivustoni",
+ name: "Nimi",
+ name_cannot_be_empty: "Nimi ei voi olla tyhjä.",
- name_cannot_contain_double_period: "Nimi ei voi olla '..'", // TODO: definition says a different thing, than the string
- // "Name can not be the '..' character." => "Nimi ei voi olla '..'-merkki." or
- // "Name can not contain the '..' character." => "Nimi ei voi sisältää merkkiä '..'."
+ name_cannot_contain_double_period: "Nimi ei voi olla '..'", // TODO: definition says a different thing, than the string
+ // "Name can not be the '..' character." => "Nimi ei voi olla '..'-merkki." or
+ // "Name can not contain the '..' character." => "Nimi ei voi sisältää merkkiä '..'."
- name_cannot_contain_period: "Nimi ei voi olla '.'", // TODO: definition says a different thing, than the string
- // "Name can not be the '.' character." => "Nimi ei voi olla '.'-merkki." or
- // "Name can not contain the '.' character." => "Nimi ei voi sisältää merkkiä '.'."
+ name_cannot_contain_period: "Nimi ei voi olla '.'", // TODO: definition says a different thing, than the string
+ // "Name can not be the '.' character." => "Nimi ei voi olla '.'-merkki." or
+ // "Name can not contain the '.' character." => "Nimi ei voi sisältää merkkiä '.'."
- name_cannot_contain_slash: "Nimi ei voi sisältää merkkiä '/'.",
- name_must_be_string: "Nimi voi olla vain merkkijono.",
- name_too_long: `Nimi ei voi olla pidempi kuin %% merkkiä.`,
- new: 'Uusi',
- new_email: 'New Email',
- new_folder: 'Uusi kansio',
- new_password: "Uusi salasana",
- new_username: "Uusi käyttäjänimi",
- no: 'Ei',
- no_dir_associated_with_site: 'Tähän osoitteeseen ei ole liitetty hakemistoa.',
- no_websites_published: "Et ole vielä julkaissut yhtään verkkosivustoa. Napsauta kansiota hiiren kakkospainikkeella aloittaaksesi.",
- ok: 'OK',
- open: "Avaa",
- open_in_new_tab: "Avaa uudessa välilehdessä",
- open_in_new_window: "Avaa uudessa ikkunassa",
+ name_cannot_contain_slash: "Nimi ei voi sisältää merkkiä '/'.",
+ name_must_be_string: "Nimi voi olla vain merkkijono.",
+ name_too_long: `Nimi ei voi olla pidempi kuin %% merkkiä.`,
+ new: "Uusi",
+ new_email: "New Email",
+ new_folder: "Uusi kansio",
+ new_password: "Uusi salasana",
+ new_username: "Uusi käyttäjänimi",
+ no: "Ei",
+ no_dir_associated_with_site:
+ "Tähän osoitteeseen ei ole liitetty hakemistoa.",
+ no_websites_published:
+ "Et ole vielä julkaissut yhtään verkkosivustoa. Napsauta kansiota hiiren kakkospainikkeella aloittaaksesi.",
+ ok: "OK",
+ open: "Avaa",
+ open_in_new_tab: "Avaa uudessa välilehdessä",
+ open_in_new_window: "Avaa uudessa ikkunassa",
- open_with: "Avaa sovelluksessa", // TODO: Context dependent
- // "Open" => "Avaa", can be "Avaa..." in this context or
- // "Open With" is often translated in the context of "Open With Application" => "Avaa Sovelluksessa"
+ open_with: "Avaa sovelluksessa", // TODO: Context dependent
+ // "Open" => "Avaa", can be "Avaa..." in this context or
+ // "Open With" is often translated in the context of "Open With Application" => "Avaa Sovelluksessa"
- original_name: 'Alkuperäinen nimi',
- original_path: 'Alkuperäinen polku',
- oss_code_and_content: "Avoimen lähdekoodin ohjelmisto ja sisältö",
- password: "Salasana",
- password_changed: "Salasana vaihdettu.",
- password_recovery_rate_limit: "Olet ylittänyt pyyntörajamme. Ole hyvä, ja odota muutama minuutti. Estääksesi tätä tapahtumasta uudelleen, vältä uudelleenlataamasta sivua liian monta kertaa.",
- password_recovery_token_invalid: "Tämä salasanan palautustunnus ei ole enää voimassa.",
- password_recovery_unknown_error: "Tuntematon virhe. Yritä myöhemmin uudelleen.",
- password_required: 'Salasana vaaditaan.',
- password_strength_error: "Salasanan tulee olla vähintään 8 merkkiä pitkä ja sisältää vähintään yhden ison kirjaimen, yhden pienen kirjaimen, yhden numeron ja yhden erikoismerkin.",
- passwords_do_not_match: '`Uusi salasana` ja `Vahvista uusi salasana` eivät täsmää.',
- paste: 'Liitä',
- paste_into_folder: "Liitä kansioon",
- path: 'Polku',
- personalization: "Personointi",
- pick_name_for_website: "Valitse nimi verkkosivustollesi:",
- picture: "Kuva",
- pictures: 'Kuvat',
- plural_suffix: 't',
- powered_by_puter_js: `Palvelun tarjoaa {{link=docs}}Puter.js{{/link}}`,
- preparing: "Valmistellaan...",
- preparing_for_upload: "Valmistellaan latausta...",
- print: 'Tulosta',
- privacy: "Yksityisyys",
- proceed_to_login: 'Jatka sisäänkirjautumiseen',
- proceed_with_account_deletion: "Jatka tilin poistamista",
- process_status_initializing: "Alustetaan",
- process_status_running: "Käynnissä",
- process_type_app: 'Sovellus',
- process_type_init: 'Alustava',
- process_type_ui: 'Käyttöliittymä',
- properties: "Ominaisuudet",
- public: 'Julkinen',
- publish: "Julkaise",
- publish_as_website: 'Julkaise verkkosivustona',
- puter_description: `Puter on yksityisyyttä korostava henkilökohtainen pilvipalvelu, jossa voit säilyttää kaikki tiedostosi, sovelluksesi ja pelisi yhdessä turvallisessa paikassa, ja jotka ovat saatavilla mistä tahansa milloin tahansa.`,
- reading_file: "Luetaan %strong%",
- recent: "Viimeisimmät",
- recommended: "Suositellut",
- recover_password: "Palauta salasanasi",
- refer_friends_c2a: "Saat 1 Gt ilmaista tallennustilaa jokaisesta ystävästä, joka luo ja vahvistaa tilin Puterissa. Myös ystäväsi saa 1 Gt:n ilmaista tallennustilaa!",
- refer_friends_social_media_c2a: `Hanki 1 Gt ilmaista tallennustilaa Puter.comista!`,
- refresh: 'Päivitä',
+ original_name: "Alkuperäinen nimi",
+ original_path: "Alkuperäinen polku",
+ oss_code_and_content: "Avoimen lähdekoodin ohjelmisto ja sisältö",
+ password: "Salasana",
+ password_changed: "Salasana vaihdettu.",
+ password_recovery_rate_limit:
+ "Olet ylittänyt pyyntörajamme. Ole hyvä, ja odota muutama minuutti. Estääksesi tätä tapahtumasta uudelleen, vältä uudelleenlataamasta sivua liian monta kertaa.",
+ password_recovery_token_invalid:
+ "Tämä salasanan palautustunnus ei ole enää voimassa.",
+ password_recovery_unknown_error:
+ "Tuntematon virhe. Yritä myöhemmin uudelleen.",
+ password_required: "Salasana vaaditaan.",
+ password_strength_error:
+ "Salasanan tulee olla vähintään 8 merkkiä pitkä ja sisältää vähintään yhden ison kirjaimen, yhden pienen kirjaimen, yhden numeron ja yhden erikoismerkin.",
+ passwords_do_not_match:
+ "`Uusi salasana` ja `Vahvista uusi salasana` eivät täsmää.",
+ paste: "Liitä",
+ paste_into_folder: "Liitä kansioon",
+ path: "Polku",
+ personalization: "Personointi",
+ pick_name_for_website: "Valitse nimi verkkosivustollesi:",
+ picture: "Kuva",
+ pictures: "Kuvat",
+ plural_suffix: "t",
+ powered_by_puter_js: `Palvelun tarjoaa {{link=docs}}Puter.js{{/link}}`,
+ preparing: "Valmistellaan...",
+ preparing_for_upload: "Valmistellaan latausta...",
+ print: "Tulosta",
+ privacy: "Yksityisyys",
+ proceed_to_login: "Jatka sisäänkirjautumiseen",
+ proceed_with_account_deletion: "Jatka tilin poistamista",
+ process_status_initializing: "Alustetaan",
+ process_status_running: "Käynnissä",
+ process_type_app: "Sovellus",
+ process_type_init: "Alustava",
+ process_type_ui: "Käyttöliittymä",
+ properties: "Ominaisuudet",
+ public: "Julkinen",
+ publish: "Julkaise",
+ publish_as_website: "Julkaise verkkosivustona",
+ puter_description: `Puter on yksityisyyttä korostava henkilökohtainen pilvipalvelu, jossa voit säilyttää kaikki tiedostosi, sovelluksesi ja pelisi yhdessä turvallisessa paikassa, ja jotka ovat saatavilla mistä tahansa milloin tahansa.`,
+ reading_file: "Luetaan %strong%",
+ recent: "Viimeisimmät",
+ recommended: "Suositellut",
+ recover_password: "Palauta salasanasi",
+ refer_friends_c2a:
+ "Saat 1 Gt ilmaista tallennustilaa jokaisesta ystävästä, joka luo ja vahvistaa tilin Puterissa. Myös ystäväsi saa 1 Gt:n ilmaista tallennustilaa!",
+ refer_friends_social_media_c2a: `Hanki 1 Gt ilmaista tallennustilaa Puter.comista!`,
+ refresh: "Päivitä",
- release_address_confirmation: `Haluatko varmasti julkaista tämän osoitteen?`, // TODO: Slight ambiguity between the meaning of "release"
- // "get rid of" => "Oletko varma, että haluat luovuttaa tämän osoitteen?" or
- // "publish" => "Oletko varma, että haluat julkaista tämän osoitteen?"
+ release_address_confirmation: `Haluatko varmasti julkaista tämän osoitteen?`, // TODO: Slight ambiguity between the meaning of "release"
+ // "get rid of" => "Oletko varma, että haluat luovuttaa tämän osoitteen?" or
+ // "publish" => "Oletko varma, että haluat julkaista tämän osoitteen?"
- remove_from_taskbar:'Poista tehtäväpalkista',
- rename: 'Nimeä uudelleen',
- repeat: 'Toista',
- replace: 'Replace',
- replace_all: 'Korvaa kaikki',
- resend_confirmation_code: "Lähetä vahvistuskoodi Uudelleen",
- reset_colors: "Palauta värit",
- restart_puter_confirm: "Haluatko varmasti käynnistää Puterin uudelleen?",
- restore: "Palauta",
- save: 'Tallenna',
- saturation: 'Kylläisyys',
- save_account: 'Tallenna tili',
- save_account_to_get_copy_link: "Luo tili jatkaaksesi.",
- save_account_to_publish: 'Luo tili jatkaaksesi.',
- save_session: 'Tallenna istunto',
- save_session_c2a: 'Luo tili tallentaaksesi nykyisen istuntosi ja välttääksesi työsi menettämisen.',
- scan_qr_c2a: 'Skannaa alla oleva koodi kirjautuaksesi tähän istuntoon muilla laitteilla.',
- scan_qr_2fa: 'Skannaa QR-koodi todennussovelluksellasi',
- scan_qr_generic: 'Skannaa tämä QR-koodi puhelimellasi tai toisella laitteella.',
- search: 'Etsi',
- seconds: 'sekuntia',
- security: "Turvallisuus",
- select: "Valitse",
- selected: 'valitut',
- select_color: 'Valitse väri…',
- sessions: "Istunnot",
- send: "Lähetä",
- send_password_recovery_email: "Lähetä salasanan palautussähköposti",
- session_saved: "Kiitos tilin luomisesta. Tämä istunto on tallennettu.",
- settings: "Asetukset",
- set_new_password: "Aseta uusi salasana",
- share: "Jaa",
+ remove_from_taskbar: "Poista tehtäväpalkista",
+ rename: "Nimeä uudelleen",
+ repeat: "Toista",
+ replace: "Replace",
+ replace_all: "Korvaa kaikki",
+ resend_confirmation_code: "Lähetä vahvistuskoodi Uudelleen",
+ reset_colors: "Palauta värit",
+ restart_puter_confirm: "Haluatko varmasti käynnistää Puterin uudelleen?",
+ restore: "Palauta",
+ save: "Tallenna",
+ saturation: "Kylläisyys",
+ save_account: "Tallenna tili",
+ save_account_to_get_copy_link: "Luo tili jatkaaksesi.",
+ save_account_to_publish: "Luo tili jatkaaksesi.",
+ save_session: "Tallenna istunto",
+ save_session_c2a:
+ "Luo tili tallentaaksesi nykyisen istuntosi ja välttääksesi työsi menettämisen.",
+ scan_qr_c2a:
+ "Skannaa alla oleva koodi kirjautuaksesi tähän istuntoon muilla laitteilla.",
+ scan_qr_2fa: "Skannaa QR-koodi todennussovelluksellasi",
+ scan_qr_generic:
+ "Skannaa tämä QR-koodi puhelimellasi tai toisella laitteella.",
+ search: "Etsi",
+ seconds: "sekuntia",
+ security: "Turvallisuus",
+ select: "Valitse",
+ selected: "valitut",
+ select_color: "Valitse väri…",
+ sessions: "Istunnot",
+ send: "Lähetä",
+ send_password_recovery_email: "Lähetä salasanan palautussähköposti",
+ session_saved: "Kiitos tilin luomisesta. Tämä istunto on tallennettu.",
+ settings: "Asetukset",
+ set_new_password: "Aseta uusi salasana",
+ share: "Jaa",
- share_to: "Jaa", // TODO: Grammatical ambiguity
- // The base form of "Share" is "Jaa". So maybe "Jaa..." is appropriate?
- // If "share to" is followed by the name of a user, it will not make any sense, as the name can be suffixed by for example "Jaa %%lle".
+ share_to: "Jaa", // TODO: Grammatical ambiguity
+ // The base form of "Share" is "Jaa". So maybe "Jaa..." is appropriate?
+ // If "share to" is followed by the name of a user, it will not make any sense, as the name can be suffixed by for example "Jaa %%lle".
- share_with: "Jaa:",
- shortcut_to: "Pikakuvake",
- show_all_windows: "Näytä kaikki ikkunat",
- show_hidden: 'Näytä piilotetut',
- sign_in_with_puter: "Kirjaudu sisään Puterilla",
- sign_up: "Rekisteröidy",
- signing_in: "Kirjaudutaan sisään…",
- size: 'Koko',
- skip: 'Ohita',
- something_went_wrong: "Jokin meni pieleen.",
- sort_by: 'Lajittele',
- start: 'Käynnistä',
- status: "Tila",
- storage_usage: "Tallennustilan käyttö",
- storage_puter_used: 'Puterin käyttämä',
- taking_longer_than_usual: 'Kestää hieman tavallista kauemmin. Ole hyvä ja odota...',
- task_manager: "Tehtävienhallinta",
- taskmgr_header_name: "Nimi",
- taskmgr_header_status: "Tila",
- taskmgr_header_type: "Tyyppi",
- terms: "Ehdot",
- text_document: 'Tekstiasiakirja',
- tos_fineprint: `Klikkaamalla 'Luo ilmainen tili' hyväksyt Puterin {{link=terms}}käyttöehdot{{/link}} ja {{link=privacy}}tietosuojakäytännön{{/link}}.`,
- transparency: "Läpinäkyvyys",
+ share_with: "Jaa:",
+ shortcut_to: "Pikakuvake",
+ show_all_windows: "Näytä kaikki ikkunat",
+ show_hidden: "Näytä piilotetut",
+ sign_in_with_puter: "Kirjaudu sisään Puterilla",
+ sign_up: "Rekisteröidy",
+ signing_in: "Kirjaudutaan sisään…",
+ size: "Koko",
+ skip: "Ohita",
+ something_went_wrong: "Jokin meni pieleen.",
+ sort_by: "Lajittele",
+ start: "Käynnistä",
+ status: "Tila",
+ storage_usage: "Tallennustilan käyttö",
+ storage_puter_used: "Puterin käyttämä",
+ taking_longer_than_usual:
+ "Kestää hieman tavallista kauemmin. Ole hyvä ja odota...",
+ task_manager: "Tehtävienhallinta",
+ taskmgr_header_name: "Nimi",
+ taskmgr_header_status: "Tila",
+ taskmgr_header_type: "Tyyppi",
+ terms: "Ehdot",
+ text_document: "Tekstiasiakirja",
+ tos_fineprint: `Klikkaamalla 'Luo ilmainen tili' hyväksyt Puterin {{link=terms}}käyttöehdot{{/link}} ja {{link=privacy}}tietosuojakäytännön{{/link}}.`,
+ transparency: "Läpinäkyvyys",
- trash: 'Roskakori', // TODO: Ambiguous meaning
- // "Trash" is oft used to just mean "Trash bin" => 'Roskakori' or
- // "Trash" by itself => 'Roska'
+ trash: "Roskakori", // TODO: Ambiguous meaning
+ // "Trash" is oft used to just mean "Trash bin" => 'Roskakori' or
+ // "Trash" by itself => 'Roska'
- two_factor: 'Kaksivaiheinen tunnistautuminen',
- two_factor_disabled: 'Kaksivaiheinen tunnistautuminen poissa käytöstä',
- two_factor_enabled: 'Kaksivaiheinen tunnistautuminen käytössä',
+ two_factor: "Kaksivaiheinen tunnistautuminen",
+ two_factor_disabled: "Kaksivaiheinen tunnistautuminen poissa käytöstä",
+ two_factor_enabled: "Kaksivaiheinen tunnistautuminen käytössä",
- type: 'Kirjoita', // TODO: Ambiguous meaning
- // "Type of an object" => 'Tyyppi' or
- // "Type on the keyboard" => 'Kirjoita'
+ type: "Kirjoita", // TODO: Ambiguous meaning
+ // "Type of an object" => 'Tyyppi' or
+ // "Type on the keyboard" => 'Kirjoita'
-
-
- type_confirm_to_delete_account: "Kirjoita 'vahvista' poistaaksesi tilisi.",
- ui_colors: "Käyttöliittymän värit",
- ui_manage_sessions: "Istunnon hallinta",
- ui_revoke: "Peruuta",
- undo: 'Kumoa',
- unlimited: 'Rajoittamaton',
- unzip: "Pura zip-tiedosto",
- upload: 'Lataa',
- upload_here: 'Lataa tähän',
- usage: 'Käyttö',
- username: "Käyttäjänimi",
- username_changed: 'Käyttäjänimi päivitetty onnistuneesti.',
- username_required: 'Käyttäjänimi vaaditaan.',
- versions: "Versiot",
- videos: 'Videot',
- visibility: 'Näkyvyys',
- yes: 'Kyllä',
- yes_release_it: 'Kyllä, julkaise se',
- you_have_been_referred_to_puter_by_a_friend: "Kaverisi on kutsunut sinut Puteriin!",
- zip: "Zip",
- zipping_file: "Zipataan %strong%",
+ type_confirm_to_delete_account: "Kirjoita 'vahvista' poistaaksesi tilisi.",
+ ui_colors: "Käyttöliittymän värit",
+ ui_manage_sessions: "Istunnon hallinta",
+ ui_revoke: "Peruuta",
+ undo: "Kumoa",
+ unlimited: "Rajoittamaton",
+ unzip: "Pura zip-tiedosto",
+ upload: "Lataa",
+ upload_here: "Lataa tähän",
+ usage: "Käyttö",
+ username: "Käyttäjänimi",
+ username_changed: "Käyttäjänimi päivitetty onnistuneesti.",
+ username_required: "Käyttäjänimi vaaditaan.",
+ versions: "Versiot",
+ videos: "Videot",
+ visibility: "Näkyvyys",
+ yes: "Kyllä",
+ yes_release_it: "Kyllä, julkaise se",
+ you_have_been_referred_to_puter_by_a_friend:
+ "Kaverisi on kutsunut sinut Puteriin!",
+ zip: "Zip",
+ zipping_file: "Zipataan %strong%",
- // === 2FA Setup ===
- setup2fa_1_step_heading: 'Avaa todennussovelluksesi',
- setup2fa_1_instructions: `
+ // === 2FA Setup ===
+ setup2fa_1_step_heading: "Avaa todennussovelluksesi",
+ setup2fa_1_instructions: `
Voit käyttää mitä tahansa todennussovellusta, joka tukee aikaperusteista kertakirjautumissalasanaa (TOTP-protokollaa).
Valittavanasi on monia sovelluksia, mutta jos et ole varma,
Authy on hyvä valinta Androidille ja iOS:lle.
`,
- setup2fa_2_step_heading: 'Skannaa QR-koodi',
- setup2fa_3_step_heading: 'Syötä kuusinumeroinen koodi',
- setup2fa_4_step_heading: 'Kopioi palautuskoodisi',
- setup2fa_4_instructions: `
+ setup2fa_2_step_heading: "Skannaa QR-koodi",
+ setup2fa_3_step_heading: "Syötä kuusinumeroinen koodi",
+ setup2fa_4_step_heading: "Kopioi palautuskoodisi",
+ setup2fa_4_instructions: `
Nämä palautuskoodit ovat ainoa tapa päästä tiliisi, jos menetät puhelimesi tai et voi käyttää todennussovellustasi.
Varmista, että säilytät ne turvallisessa paikassa.
`,
- setup2fa_5_step_heading: 'Vahvista kaksivaiheisen tunnistautumisen asetukset',
- setup2fa_5_confirmation_1: 'Olen tallentanut palautuskoodini turvalliseen paikkaan',
- setup2fa_5_confirmation_2: 'Olen valmis ottamaan kaksivaiheisen tunnistautumisen käyttöön',
- setup2fa_5_button: 'Ota kaksivaiheinen tunnistautuminen käyttöön',
+ setup2fa_5_step_heading:
+ "Vahvista kaksivaiheisen tunnistautumisen asetukset",
+ setup2fa_5_confirmation_1:
+ "Olen tallentanut palautuskoodini turvalliseen paikkaan",
+ setup2fa_5_confirmation_2:
+ "Olen valmis ottamaan kaksivaiheisen tunnistautumisen käyttöön",
+ setup2fa_5_button: "Ota kaksivaiheinen tunnistautuminen käyttöön",
+ // === 2FA Login ===
+ login2fa_otp_title: "Syötä kaksivaiheisen tunnistautumisen koodi",
+ login2fa_otp_instructions:
+ "Syötä kuusinumeroinen koodi todennussovelluksestasi.",
+ login2fa_recovery_title: "Syötä palautuskoodi",
+ login2fa_recovery_instructions:
+ "Syötä yksi palautuskoodeistasi saadaksesi pääsy tilillesi.",
+ login2fa_use_recovery_code: "Käytä palautuskoodi",
+ login2fa_recovery_back: "Takaisin",
+ login2fa_recovery_placeholder: "XXXXXXXX",
- // === 2FA Login ===
- login2fa_otp_title: 'Syötä kaksivaiheisen tunnistautumisen koodi',
- login2fa_otp_instructions: 'Syötä kuusinumeroinen koodi todennussovelluksestasi.',
- login2fa_recovery_title: 'Syötä palautuskoodi',
- login2fa_recovery_instructions: 'Syötä yksi palautuskoodeistasi saadaksesi pääsy tilillesi.',
- login2fa_use_recovery_code: 'Käytä palautuskoodi',
- login2fa_recovery_back: 'Takaisin',
- login2fa_recovery_placeholder: 'XXXXXXXX',
+ change: "muutos", // In English: "Change"
+ clock_visibility: "kellon näkyvyys", // In English: "Clock Visibility"
+ reading: "lukeminen", // In English: "Reading %strong%"
+ writing: "kirjoittaminen", // In English: "Writing %strong%"
+ unzipping: "purkaminen", // In English: "Unzipping %strong%"
+ sequencing: "järjestäminen", // In English: "Sequencing %strong%"
+ zipping: "pakkaaminen", // In English: "Zipping %strong%"
+ Editor: "Muokkaaja", // In English: "Editor"
+ Viewer: "Katselija", // In English: "Viewer"
+ "People with access": "Henkilöt, joilla on käyttöoikeus", // In English: "People with access"
+ "Share With…": "Jaa kanssa…", // In English: "Share With…"
+ Owner: "Omistaja", // In English: "Owner"
+ "You can't share with yourself.": "Et voi jakaa itsellesi.", // In English: "You can't share with yourself."
+ "This user already has access to this item":
+ "Tällä käyttäjällä on jo pääsy tähän kohteeseen", // In English: "This user already has access to this item"
-
+ "billing.change_payment_method": "Vaihda", // In English: "Change"
+ "billing.cancel": "Peruuta", // In English: "Cancel"
+ "billing.download_invoice": "Lataa", // In English: "Download"
+ "billing.payment_method": "Maksutapa", // In English: "Payment Method"
+ "billing.payment_method_updated": "Maksutapa päivitetty!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Vahvista maksutapa", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Maksuhistoria", // In English: "Payment History"
+ "billing.refunded": "Hyvitetty", // In English: "Refunded"
+ "billing.paid": "Maksettu", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Jatka tilausta", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Tilauksesi on peruutettu.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Voit jatkaa tilauksesi käyttöä laskutuskauden loppuun asti.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Ilmainen", // In English: "Free"
+ "billing.offering.pro": "Ammattilainen", // In English: "Professional"
+ "billing.offering.business": "Yritys", // In English: "Business"
+ "billing.cloud_storage": "Pilvitallennustila", // In English: "Cloud Storage"
+ "billing.ai_access": "Tekoälykäyttö", // In English: "AI Access"
+ "billing.bandwidth": "Kaistanleveys", // In English: "Bandwidth"
+ "billing.apps_and_games": "Sovellukset ja pelit", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Vaihda %strong% tilaukseen", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Vaiha %strong% tilaukseen", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Maksuasetukset", // In English: "Payment Setup"
+ "billing.back": "Takaisin", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Olet nyt tilannut %strong% tason.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Olet nyt tehnyt tilauksen", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Haluatko varmasti peruuttaa tilauksesi?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Tilauksen määritys", // In English: "Subscription Setup"
+ "billing.cancel_it": "Peruuta", // In English: "Cancel It"
+ "billing.keep_it": "Säilytä", // In English: "Keep It"
+ "billing.subscription_resumed": "%strong% -tilaustasi on jatkettu.", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Päivitä nyt", // In English: "Upgrade Now"
+ "billing.upgrade": "Päivitä", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Olet tällä hetkellä ilmaisella suunnitelmalla.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Lataa kuitti", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Tilauksesi tarkistuksessa tapahtui virhe.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Sähköpostiosoitettasi ei ole vahvistettu. Lähetämme sinulle vahvistuskoodin nyt.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Olet peruuttanut tilauksesi, ja se vaihtuu automaattisesti ilmaiseen tasoon laskutuskauden lopussa. Sinulta ei veloiteta enää, ellet päätä uusia tilaustasi.", // 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": "Nykyinen suunnitelmasi on voimassa tämän laskutuskauden loppuun asti.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Nykyinen suunnitelma", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Peruutettu tilaus (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Hallinnoi", // In English: "Manage"
+ "billing.limited": "Rajallinen", // In English: "Limited"
+ "billing.expanded": "Laajennettu", // In English: "Expanded"
+ "billing.accelerated": "Nopeutettu", // In English: "Accelerated"
+ "billing.enjoy_msg": "Nauti %% pilvitallennustilasta ja muista eduista.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
+ },
+};
- "change": "Muuta", // In English: "Change"
- "clock_visibility": "Kellon näkyvyys", // In English: "Clock Visibility"
- "reading": "Luetaan %strong%", // In English: "Reading %strong%"
- "writing": "Kirjoitetaan %strong%", // In English: "Writing %strong%"
- "unzipping": "Puretaan %strong%", // In English: "Unzipping %strong%"
- "sequencing": "Järjestetään %strong%", // In English: "Sequencing %strong%"
- "zipping": "Paketoidaan %strong%", // In English: "Zipping %strong%"
- "Editor": "Muokkaaja", // In English: "Editor"
- "Viewer": "Katsoja", // In English: "Viewer"
- "People with access": "Henkilöt, joilla on oikeudet", // In English: "People with access"
- "Share With…": "Jaa kanssa…", // In English: "Share With…"
- "Owner": "Omistaja", // In English: "Owner"
- "You can't share with yourself.": "Et voi jakaa itsellesi.", // In English: "You can't share with yourself."
- "This user already has access to this item": "Käyttäjällä on jo oikeudet tähän kohteeseen.", // In English: "This user already has access to this item"
- }
-}
export default fi;
diff --git a/src/gui/src/i18n/translations/fr.js b/src/gui/src/i18n/translations/fr.js
index e9321eac11..cd5066163a 100644
--- a/src/gui/src/i18n/translations/fr.js
+++ b/src/gui/src/i18n/translations/fr.js
@@ -362,6 +362,56 @@ const fr = {
"Owner": 'Propriétaire',
"You can't share with yourself.": 'Vous ne pouvez pas partager avec vous-même',
"This user already has access to this item": 'Cet utilisateur à déja accès à cet élément',
+
+ // ----------------------------------------
+ // translations:
+ // ----------------------------------------
+ "billing.change_payment_method": "Modifier le mode de paiement", // In English: "Change"
+ "billing.cancel": "Annuler", // In English: "Cancel"
+ "billing.download_invoice": "Télécharger la facture", // In English: "Download"
+ "billing.payment_method": "Mode de paiement", // In English: "Payment Method"
+ "billing.payment_method_updated": "Mode de paiement mis à jour !", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Confirmer le mode de paiement", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Historique des paiements", // In English: "Payment History"
+ "billing.refunded": "Remboursé", // In English: "Refunded"
+ "billing.paid": "Payé", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Reprendre l'abonnement", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Votre abonnement a été annulé.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Vous aurez toujours accès à votre abonnement jusqu'à la fin de cette période de facturation.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Gratuit", // In English: "Free"
+ "billing.offering.pro": "Professionnel", // In English: "Professional"
+ "billing.offering.business": "Entreprise", // In English: "Business"
+ "billing.cloud_storage": "Stockage Cloud", // In English: "Cloud Storage"
+ "billing.ai_access": "Accès à l'IA", // In English: "AI Access"
+ "billing.bandwidth": "Bande passante", // In English: "Bandwidth"
+ "billing.apps_and_games": "Applications et jeux", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Passer à %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Passer à %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Configuration du paiement", // In English: "Payment Setup"
+ "billing.back": "Retour", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Vous êtes maintenant abonné au niveau %strong%.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Vous êtes maintenant abonné", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Êtes-vous sûr de vouloir annuler votre abonnement ?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Configuration de l'abonnement", // In English: "Subscription Setup"
+ "billing.cancel_it": "L'annuler", // In English: "Cancel It"
+ "billing.keep_it": "Le conserver", // In English: "Keep It"
+ "billing.subscription_resumed": "Votre abonnement %strong% a été repris !", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Mettre à niveau maintenant", // In English: "Upgrade Now"
+ "billing.upgrade": "Mettre à niveau", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Vous êtes actuellement sur le plan gratuit.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Télécharger le reçu", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Un problème est survenu lors de la vérification de votre statut d'abonnement.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Votre e-mail n'a pas été confirmé. Nous allons vous envoyer un code pour le confirmer maintenant.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Vous avez annulé votre abonnement et il passera automatiquement au niveau gratuit à la fin de la période de facturation. Vous ne serez pas facturé à nouveau sauf si vous vous réabonnez.", // 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": "Votre plan actuel jusqu'à la fin de cette période de facturation.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Plan actuel", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Abonnement annulé (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Gérer", // In English: "Manage"
+ "billing.limited": "Limité", // In English: "Limited"
+ "billing.expanded": "Étendu", // In English: "Expanded"
+ "billing.accelerated": "Accéléré", // In English: "Accelerated"
+ "billing.enjoy_msg": "Profitez de %% de stockage cloud et d'autres avantages.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/he.js b/src/gui/src/i18n/translations/he.js
index 57d356c46c..61312e4085 100644
--- a/src/gui/src/i18n/translations/he.js
+++ b/src/gui/src/i18n/translations/he.js
@@ -377,7 +377,54 @@ const en = {
"Share With…": "שתף עם…", // In English: "Share With…"
"Owner": "בעלים", // In English: "Owner"
"You can't share with yourself.": "אינך יכול לשתף עם עצמך.", // In English: "You can't share with yourself."
- "This user already has access to this item": "למשתמש זה כבר יש גישה לפריט זה" // In English: "This user already has access to this item"
+ "This user already has access to this item": "למשתמש זה כבר יש גישה לפריט זה", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "שינוי", // In English: "Change"
+ "billing.cancel": "ביטול", // In English: "Cancel"
+ "billing.download_invoice": "הורדה", // In English: "Download"
+ "billing.payment_method": "שיטת תשלום", // In English: "Payment Method"
+ "billing.payment_method_updated": "שיטת תשלום עודכנה!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "אישור שיטת תשלום", // In English: "Confirm Payment Method"
+ "billing.payment_history": "היסטוריית תשלומים", // In English: "Payment History"
+ "billing.refunded": "הוחזר", // In English: "Refunded"
+ "billing.paid": "שולם", // In English: "Paid"
+ "billing.ok": "אוקיי", // In English: "OK"
+ "billing.resume_subscription": "חידוש מנוי", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "המנוי שלכם בוטל", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "עדיין תהיה לכם גישה למנוי עד סוף תקופת החיוב.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "חינם", // In English: "Free"
+ "billing.offering.pro": "מקצועי", // In English: "Professional"
+ "billing.offering.business": "עסקי", // In English: "Business"
+ "billing.cloud_storage": "אחסון בענן", // In English: "Cloud Storage"
+ "billing.ai_access": "גישה לAI", // In English: "AI Access"
+ "billing.bandwidth": "רוחב פס", // In English: "Bandwidth"
+ "billing.apps_and_games": "אפליקציות ומשחקים", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "שדרוג ל %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "שינוי ל %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "הגדרות תשלום", // In English: "Payment Setup"
+ "billing.back": "חזרה", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "אתם עכשיו מנויים למסלול %strong%", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "אתם עכשיו מנויים", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "אתם בטוחים שאתם מעוניינים לבטל את המנוי?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "הגדרות מנוי", // In English: "Subscription Setup"
+ "billing.cancel_it": "ביטול", // In English: "Cancel It"
+ "billing.keep_it": "שמירה", // In English: "Keep It"
+ "billing.subscription_resumed": "%strong% המנוי שוחזר!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "שדרגו עכשיו", // In English: "Upgrade Now"
+ "billing.upgrade": "שדרוג", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "אתם כרגע בתכנית החינמית", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "הורדת קבלה", // In English: "Download Receipt"
+ "billing.subscription_check_error": "קרתה תקלה בזמן בדיקת סטאטוס המנוי שלכם.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "האימייל שלכם לא אומת. נשלח עכשיו קוד אימות לאימייל", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "ביטלתם את המנוי והוא אוטומתית יעבור למנוי חינמי בסוף תקופת החיוב. לא יתבצעו עוד חיובים אלא אם תשחזרו את המנוי", // 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": "המנוי שלכם עד סוף תקופת החיוב.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "תכנית עכשווית", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "ביטול מנוי (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "ניהול", // In English: "Manage"
+ "billing.limited": "מוגבל", // In English: "Limited"
+ "billing.expanded": "מורחב", // In English: "Expanded"
+ "billing.accelerated": "מואץ", // In English: "Accelerated"
+ "billing.enjoy_msg": "תהנו מ %% של אחסון ענן בנוסף להטבות נוספות", // In English: "Enjoy %% of Cloud Storage plus other benefits."
},
};
diff --git a/src/gui/src/i18n/translations/hi.js b/src/gui/src/i18n/translations/hi.js
index d00084cd8d..49fd1a5d1b 100644
--- a/src/gui/src/i18n/translations/hi.js
+++ b/src/gui/src/i18n/translations/hi.js
@@ -353,6 +353,54 @@ const hi = {
"Owner": "मालिक", // In English: "Owner"
"You can't share with yourself.": "आप अपने आप के साथ साझा नहीं कर सकते।", // In English: "You can't share with yourself."
"This user already has access to this item": "इस उपयोगकर्ता के पास पहले से ही इस वस्तु का प्रवेश है", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "बदलें", // In English: "Change"
+ "billing.cancel": "रद्द करें", // In English: "Cancel"
+ "billing.download_invoice": "डाउनलोड करें", // In English: "Download"
+ "billing.payment_method": "भुगतान की विधि", // In English: "Payment Method"
+ "billing.payment_method_updated": "भुगतान विधि अद्यतन किया गया!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "भुगतान विधि की पुष्टि करें।", // In English: "Confirm Payment Method"
+ "billing.payment_history": "भुगतान इतिहास", // In English: "Payment History"
+ "billing.refunded": "धनवापसी पूरी हुई।", // In English: "Refunded"
+ "billing.paid": "भुगतान चुकाया गया है।", // In English: "Paid"
+ "billing.ok": "ठीक है।", // In English: "OK"
+ "billing.resume_subscription": "सदस्यता फिर से शुरू करें।", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "आपकी सदस्यता रद्द कर दी गई है।", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "इस विधेयक अवधि के अंत तक आप अपनी सदस्यता का उपयोग कर पाएंगे।", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "मुक्त", // In English: "Free"
+ "billing.offering.pro": "पेशेवर", // In English: "Professional"
+ "billing.offering.business": "व्यापार", // In English: "Business"
+ "billing.cloud_storage": "क्लाउड स्टोरेज", // In English: "Cloud Storage"
+ "billing.ai_access": "एआई पहुँच", // In English: "AI Access"
+ "billing.bandwidth": "डाटा संचरण क्षमता", // In English: "Bandwidth"
+ "billing.apps_and_games": "अनुप्रयोग और खेल", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "%strong% में अपग्रेड करें", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "%strong% पर बदलें", // In English: "Switch to %strong%"
+ "billing.payment_setup": "भुगतान व्यवस्था", // In English: "Payment Setup"
+ "billing.back": "पीछे", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "अब आप %strong% स्तर की सदस्यता ले चुके हैं।", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "अब आप सदस्य बन चुके हैं।", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "क्या आप वाकई अपनी सदस्यता रद्द करना चाहते हैं?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "सदस्यता व्यवस्था", // In English: "Subscription Setup"
+ "billing.cancel_it": "इसे रद्द करें", // In English: "Cancel It"
+ "billing.keep_it": "इसे रखें", // In English: "Keep It"
+ "billing.subscription_resumed": "आपकी %strong% सदस्यता फिर से सक्रिय हो गई है!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "अभी उन्नत करें।", // In English: "Upgrade Now"
+ "billing.upgrade": "उन्नति करें", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "आप वर्तमान में मुफ्त योजना पर हैं।", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "रसीद डाउनलोड करें", // In English: "Download Receipt"
+ "billing.subscription_check_error": "आपकी सदस्यता स्थिति जांचते समय एक समस्या हुई।", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "आपका ईमेल सत्यापित नहीं हुआ है। हम इसे सत्यापित करने के लिए आपको एक कोड भेजेंगे।", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "आपने अपनी सदस्यता रद्द कर दी है और यह विधेयक अवधि के अंत में स्वचालित रूप से मुफ्त योजना पर बदल जाएगी। जब तक आप फिर से सदस्यता नहीं लेते, तब तक आपसे फिर से शुल्क नहीं लिया जाएगा।", // 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": "आपकी वर्तमान योजना इस विधेयक अवधि के अंत तक।", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "वर्तमान योजना", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "रद्द की गई (%%) सदस्यता", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "प्रबंधन", // In English: "Manage"
+ "billing.limited": "सीमित", // In English: "Limited"
+ "billing.expanded": "विस्तारित ", // In English: "Expanded"
+ "billing.accelerated": "त्वरित ", // In English: "Accelerated"
+ "billing.enjoy_msg": "%% क्लाउड स्टोरेज का आनंद लें और अन्य लाभ प्राप्त करें।", // In English: "Enjoy %% of Cloud Storage plus other benefits."
+
}
};
diff --git a/src/gui/src/i18n/translations/hu.js b/src/gui/src/i18n/translations/hu.js
index e454312c9b..01fe9412ae 100644
--- a/src/gui/src/i18n/translations/hu.js
+++ b/src/gui/src/i18n/translations/hu.js
@@ -334,7 +334,7 @@ const hu = {
login2fa_recovery_back: "Vissza",
login2fa_recovery_placeholder: "XXXXXXXX",
- "change": "váltás", // In English: "Change"
+ "change": "Módosítás", // In English: "Change"
"clock_visibility": "Óra Megjelenítése", // In English: "Clock Visibility"
"reading": "Olvasás %strong%", // In English: "Reading %strong%"
"writing": "Írás %strong%", // In English: "Writing %strong%"
@@ -348,6 +348,55 @@ const hu = {
"Owner": "Tulajdonos", // In English: "Owner"
"You can't share with yourself.": "Nem oszthatod meg magaddal.", // In English: "You can't share with yourself."
"This user already has access to this item": "Ez a felhasználó már hozzáfér ehhez az elemhez", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "Módosítás", // In English: "Change"
+ "billing.cancel": "Lemondás", // In English: "Cancel"
+ "billing.change_payment_method": "Fizetési mód megváltoztatása", // In English: "Change"
+ "billing.cancel": "Mégse", // In English: "Cancel"
+ "billing.download_invoice": "Letöltés", // In English: "Download"
+ "billing.payment_method": "Fizetési mód", // In English: "Payment Method"
+ "billing.payment_method_updated": "A fizetési mód frissítve!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Fizetési mód megerősítése", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Fizetési előzmények", // In English: "Payment History"
+ "billing.refunded": "Visszatérítve", // In English: "Refunded"
+ "billing.paid": "Fizetve", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Előfizetés folytatása", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Az előfizetésed lemondva.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Az aktuális számlázási időszak végéig továbbra is hozzáférsz az előfizetésedhez.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Ingyenes", // In English: "Free"
+ "billing.offering.pro": "Professzionális", // In English: "Professional"
+ "billing.offering.business": "Üzleti", // In English: "Business"
+ "billing.cloud_storage": "Felhő Tárhely", // In English: "Cloud Storage"
+ "billing.ai_access": "AI hozzáférés", // In English: "AI Access"
+ "billing.bandwidth": "Sávszélesség", // In English: "Bandwidth"
+ "billing.apps_and_games": "Alkalmazások és játékok", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Csomag váltása erre: %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Váltás erre: %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Fizetési beállítás", // In English: "Payment Setup"
+ "billing.back": "Vissza", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Mostantól feliratkoztál a %strong% csomagra.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Mostantól előfizettél", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Biztos, hogy le akarod mondani az előfizetésedet?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Előfizetés beállítása", // In English: "Subscription Setup"
+ "billing.cancel_it": "Mondd le", // In English: "Cancel It"
+ "billing.keep_it": "Tartsd meg", // In English: "Keep It"
+ "billing.subscription_resumed": "A(z) %strong% előfizetésed folytatva lett!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Válts magasabb csomagra most", // In English: "Upgrade Now"
+ "billing.upgrade": "Csomag Váltása", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Jelenleg az ingyenes csomagon vagy.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Nyugta letöltése", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Hiba történt az előfizetési állapot ellenőrzése közben.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Az e-mail címed még nincs megerősítve. Most küldünk egy kódot a megerősítéshez.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Lemondtad az előfizetésedet, és a számlázási időszak végén automatikusan az ingyenes csomagra vált. Nem fogsz újra díjat fizetni, hacsak nem fizetsz elő újra.", // 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": "Az aktuális csomagod a számlázási időszak végéig.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Jelenlegi csomag", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Lemondott Előfizetés (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Kezelés", // In English: "Manage"
+ "billing.limited": "Korlátozott", // In English: "Limited"
+ "billing.expanded": "Bővített", // In English: "Expanded"
+ "billing.accelerated": "Gyorsított", // In English: "Accelerated"
+ "billing.enjoy_msg": "Élvezd a %% Felhőtárhelyet és további előnyöket.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/hy.js b/src/gui/src/i18n/translations/hy.js
index e2b54d8539..98894d73a0 100644
--- a/src/gui/src/i18n/translations/hy.js
+++ b/src/gui/src/i18n/translations/hy.js
@@ -346,20 +346,67 @@ const hy = {
login2fa_recovery_back: "Հետ",
login2fa_recovery_placeholder: "XXXXXXXX",
- "change": "փոփոխել", // In English: "Change"
- "clock_visibility": "ժամացույցի տեսանելիություն", // In English: "Clock Visibility"
- "reading": "կարդալ", // In English: "Reading %strong%"
- "writing": "գրել", // In English: "Writing %strong%"
- "unzipping": "Արխիվի բացում", // In English: "Unzipping %strong%"
- "sequencing": "հաջորդականացում", // In English: "Sequencing %strong%"
- "zipping": "Արխիվացում", // In English: "Zipping %strong%"
- "Editor": "Խմբագիր", // In English: "Editor"
- "Viewer": "Դիտող", // In English: "Viewer"
- "People with access": "Մարդիկ, ովքեր ունեն հասանելիություն", // In English: "People with access"
- "Share With…": "Կիսվել …", // In English: "Share With…"
- "Owner": "Սեփականատեր", // In English: "Owner"
- "You can't share with yourself.": "Դուք չեք կարող կիսվել ինքներդ ձեզ հետ։", // In English: "You can't share with yourself."
- "This user already has access to this item": "Այս օգտատերը արդեն ունի հասանելիություն այս տարրին։", // In English: "This user already has access to this item"
+ change: "Փոփոխել",
+ clock_visibility: "Ժամացույցի տեսանելիություն",
+ reading: "Ընթերցում",
+ writing: "Գրում",
+ unzipping: "Արխիվը բացել",
+ sequencing: "Հաջորդականություն",
+ zipping: "Արխիվացում",
+ Editor: "Խմբագրիչ",
+ Viewer: "Դիտորդ",
+ "People with access": "Մուտքի իրավունք ունեցող անձինք",
+ "Share With…": "Կիսվել…",
+ Owner: "Սեփականատեր",
+ "You can’t share with yourself.": "Դուք չեք կարող կիսվել ինքներդ ձեզ հետ։",
+ "This user already has access to this item": "Այս օգտատերն արդեն մուտքի իրավունք ունի։",
+ "You can't share with yourself.": "Դուք չեք կարող կիսվել ինքներդ ձեզ հետ",
+ "billing.change_payment_method": "Փոխել",
+ "billing.cancel": "Չեղարկել",
+ "billing.download_invoice": "Ներբեռնել",
+ "billing.payment_method": "Վճարման եղանակ",
+ "billing.payment_method_updated": "Վճարման եղանակը թարմացվել է",
+ "billing.confirm_payment_method": "Հաստատեք վճարման եղանակը",
+ "billing.payment_history": "Վճարումների պատմություն",
+ "billing.refunded": "Վերադարձվել է",
+ "billing.paid": "Վճարված",
+ "billing.ok": "Լավ",
+ "billing.resume_subscription": "Երկարացնել բաժանորդագրությունը",
+ "billing.subscription_cancelled": "Ձեր բաժանորդագրությունը չեղարկվել է",
+ "billing.subscription_cancelled_description": "Դուք դեռ կունենաք ձեր բաժանորդագրությանը մուտք մինչև այս վճարային ժամանակահատվածի ավարտը",
+ "billing.offering.free": "Անվճար",
+ "billing.offering.pro": "Պրոֆեսիոնալ",
+ "billing.offering.business": "Բիզնես",
+ "billing.cloud_storage": "Ամպային պահեստավորում",
+ "billing.ai_access": "ԱԲ հասանելիություն",
+ "billing.bandwidth": "Լայնաշերտ",
+ "billing.apps_and_games": "Ծրագրեր և խաղեր",
+ "billing.upgrade_to_pro": "Թարմացնել %strong%",
+ "billing.switch_to": "Անցնել դեպի %strong%",
+ "billing.payment_setup": "Վճարման կարգավորում",
+ "billing.back": "Հետ",
+ "billing.you_are_now_subscribed_to": "Դուք այժմ բաժանորդագրված եք %strong% պլանին",
+ "billing.you_are_now_subscribed_to_without_tier": "Դուք այժմ բաժանորդագրված եք",
+ "billing.subscription_cancellation_confirmation": "Վստա՞հ եք, որ ցանկանում եք չեղարկել բաժանորդագրությունը",
+ "billing.subscription_setup": "Բաժանորդագրության կարգավորում",
+ "billing.cancel_it": "Չեղարկել",
+ "billing.keep_it": "Պահպանել",
+ "billing.subscription_resumed": "Ձեր %strong% բաժանորդագրությունը վերականգնվել է",
+ "billing.upgrade_now": "Թարմացնել հիմա",
+ "billing.upgrade": "Թարմացնել",
+ "billing.currently_on_free_plan": "Դուք ներկայումս գտնվում եք անվճար պլանի վրա",
+ "billing.download_receipt": "Ներբեռնել անդորրագիրը",
+ "billing.subscription_check_error": "Պրոբլեմ առաջացավ բաժանորդագրության կարգավիճակը ստուգելիս",
+ "billing.email_confirmation_needed": "Ձեր էլ. փոստը դեռևս հաստատված չէ։ Մենք կուղարկենք հաստատման կոդ հիմա",
+ "billing.sub_cancelled_but_valid_until": "Դուք չեղարկել եք ձեր բաժանորդագրությունը, և այն ավտոմատ կփոխվի անվճար պլանի՝ վճարային ժամանակահատվածի ավարտին։ Ձեզնից այլևս չի գանձվի, եթե նորից չբաժանորդագրվեք",
+ "billing.current_plan_until_end_of_period": "Ձեր ընթացիկ պլանը մինչև վճարային ժամանակահատվածի ավարտը",
+ "billing.current_plan": "Ընթացիկ պլան",
+ "billing.cancelled_subscription_tier": "Չեղարկված բաժանորդագրություն (%%)",
+ "billing.manage": "Կառավարել",
+ "billing.limited": "Սահմանափակված",
+ "billing.expanded": "Ընդլայնված",
+ "billing.accelerated": "Արագացված",
+ "billing.enjoy_msg": "Վայելեք %% ամպային պահեստավորում և այլ առավելություններ",
}
};
diff --git a/src/gui/src/i18n/translations/id.js b/src/gui/src/i18n/translations/id.js
index d451b49747..b838923193 100644
--- a/src/gui/src/i18n/translations/id.js
+++ b/src/gui/src/i18n/translations/id.js
@@ -380,10 +380,56 @@ const id = {
"People with access": "Orang yang memiliki akses", // In English: "People with access"
"Share With…": "Bagikan Dengan...", // In English: "Share With…"
"Owner": "Pemilik", // In English: "Owner"
- "You can't share with yourself.":
- "Anda tidak bisa membaginya dengan diri sendiri.", // In English: "You can't share with yourself."
- "This user already has access to this item":
- "Pengguna ini telah memiliki akses ke barang ini", // In English: "This user already has access to this item"
+ "You can't share with yourself.": "Anda tidak bisa membaginya dengan diri sendiri.", // In English: "You can't share with yourself."
+ "This user already has access to this item": "Pengguna ini telah memiliki akses ke barang ini", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "Ubah", // In English: "Change"
+ "billing.cancel": "Batal", // In English: "Cancel"
+ "billing.download_invoice": "Unduh", // In English: "Download"
+ "billing.payment_method": "Metode Pembayaran", // In English: "Payment Method"
+ "billing.payment_method_updated": "Metode pembayaran diperbarui!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Konfirmasi Metode Pembayaran", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Riwayat Pembayaran", // In English: "Payment History"
+ "billing.refunded": "Dikembalikan", // In English: "Refunded"
+ "billing.paid": "Dibayar", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Lanjutkan Berlangganan", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Langganan Anda telah dibatalkan.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Anda masih memiliki akses ke langganan Anda hingga akhir periode penagihan ini.", // 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": "Profesional", // In English: "Professional"
+ "billing.offering.business": "Bisnis", // In English: "Business"
+ "billing.cloud_storage": "Penyimpanan Cloud", // In English: "Cloud Storage"
+ "billing.ai_access": "Akses AI", // In English: "AI Access"
+ "billing.bandwidth": "Bandwidth", // In English: "Bandwidth"
+ "billing.apps_and_games": "Aplikasi & Game", // In English: "Apps & Games"
+ "billing.switch_to": "Beralih ke %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Pengaturan Pembayaran", // In English: "Payment Setup"
+ "billing.back": "Kembali", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Anda sekarang berlangganan paket %strong%.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Anda sekarang berlangganan", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Apakah Anda yakin ingin membatalkan langganan Anda?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Pengaturan Langganan", // In English: "Subscription Setup"
+ "billing.cancel_it": "Batalkan", // In English: "Cancel It"
+ "billing.keep_it": "Pertahankan", // In English: "Keep It"
+ "billing.subscription_resumed": "Langganan %strong% Anda telah dilanjutkan!", // In English: "Your %strong% subscription has been resumed!"
+ //Note: literal translation of 'upgrade' is 'tingkatkan' but for this context 'upgrade' more commonly used
+ "billing.upgrade": "Upgrade", // In English: "Upgrade"
+ "billing.upgrade_to_pro": "Upgrade ke %strong%", // In English: "Upgrade to %strong%"
+ "billing.upgrade_now": "Upgrade Sekarang", // In English: "Upgrade Now"
+ "billing.currently_on_free_plan": "Anda saat ini menggunakan paket gratis.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Unduh Bukti Pembayaran", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Terjadi masalah saat memeriksa status langganan Anda.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Email Anda belum dikonfirmasi. Kami akan mengirimkan kode untuk mengonfirmasinya sekarang.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Anda telah membatalkan langganan Anda dan secara otomatis akan beralih ke paket gratis di akhir periode penagihan. Anda tidak akan dikenakan biaya lagi kecuali Anda berlangganan ulang.", // 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": "Paket Anda saat ini berlaku hingga akhir periode penagihan ini.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Paket saat ini", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Langganan Dibatalkan (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Kelola", // In English: "Manage"
+ "billing.limited": "Terbatas", // In English: "Limited"
+ "billing.expanded": "Diperluas", // In English: "Expanded"
+ "billing.accelerated": "Dipercepat", // In English: "Accelerated"
+ "billing.enjoy_msg": "Nikmati %% dari Penyimpanan Cloud dan manfaat lainnya." // In English: "Enjoy %% of Cloud Storage plus other benefits."
},
};
diff --git a/src/gui/src/i18n/translations/ig.js b/src/gui/src/i18n/translations/ig.js
index c6157e03f5..75d6b8ffa2 100644
--- a/src/gui/src/i18n/translations/ig.js
+++ b/src/gui/src/i18n/translations/ig.js
@@ -18,350 +18,429 @@
*/
const ig = {
- name: "Igbo",
- english_name: "Igbo",
- code: "ig",
- dictionary: {
- about: "banyere",
- account: "akaụntụ",
- account_password: "nyochaa paswọọdụ akaụntụ",
- access_granted_to: "Enyere ohere",
- add_existing_account: "Tinye Akaụntụ dị adị",
- all_fields_required: 'A chọrọ mpaghara niile.',
- allow: 'ekwe',
- apply: "Tinye",
- ascending: 'Na-arịgo',
- associated_websites: "Weebụsaịtị Ejikọtara",
- auto_arrange: 'ndokwa onwe',
- background: "ndabere",
- browse: "Chọgharịa",
- cancel: 'Kagbuo',
- center: 'etiti',
- change_desktop_background: 'ịgbanwe ndabere desktọpụ…',
- change_email: "ịgbanwe email",
- change_language: "ịgbanwe Asụsụ",
- change_password: "ịgbanwe paswọọdụ",
- change_ui_colors: "ịgbanwe agba UI",
- change_username: "ịgbanwe aha njirimara",
- close: 'mmechi',
- close_all_windows: "mmechi Windows niile",
- close_all_windows_confirm: "Ị ji n'aka na ị chọrọ imechi windo niile?",
- close_all_windows_and_log_out: 'mmechi Windows na wee pụọ',
- change_always_open_with: "Ị chọrọ I na mepee ụdị faịlụ site na",
- color: 'Agba',
- confirm: 'gosi',
- confirm_2fa_setup: 'Etinyela m koodu ahụ na ngwa nyocha m',
- confirm_2fa_recovery: 'Echekwala m koodu mgbake m na ebe echedoro',
- hue: 'Hue',
- confirm_account_for_free_referral_storage_c2a: 'Mepụta akaụntụ wee kwado adreesị ozi-e gị iji nweta 1 GB nke nchekwa efu. Enyi gị ga-enwetakwa 1 GB nke nchekwa efu.',
- confirm_code_generic_incorrect: "Koodu ezighi ezi.",
- confirm_code_generic_too_many_requests: "Ọtụtụ ọchịchọ. Biko chere nkeji ole na ole.",
- confirm_code_generic_submit: "Nye koodu",
- confirm_code_generic_try_again: "Nwa ọzọ",
- confirm_code_generic_title: "Tinye koodu Nkwenye",
- confirm_code_2fa_instruction: "Tinye koodu ọnụọgụ isi site na ngwa nyocha gị.",
- confirm_code_2fa_submit_btn: "tinye",
- confirm_code_2fa_title: "Tinye koodu 2FA",
- confirm_delete_multiple_items: "Ị ji n'aka na ịchọrọ ihichapụ ihe ndị a kpamkpam?",
- confirm_delete_single_item: 'Ịchọrọ ihichapụ ihe a kpamkpam?',
- confirm_open_apps_log_out: "Ị nwere ngwa mepere emepe. Ị ji n'aka na ị chọrọ ịpụ?",
- confirm_new_password: "Kwenye paswọọdụ ọhụrụ",
- confirm_delete_user: "Ị ji n'aka na ịchọrọ ihichapụ akaụntụ gị? A ga-ehichapụ faịlụ gị niile na data gị kpamkpam. Enweghị ike imegharị ihe a.",
- confirm_delete_user_title: "Hichapụ Akaụntụ?",
- confirm_session_revoke: "O doro gị anya na ịchọrọ kagbuo nnọkọ a?",
- confirm_your_email_address: "Kwenye na adreesị email",
- contact_us: "Kpọtụrụ anyị",
- contact_us_verification_required: "Ị ga-enwerịrị adreesị email ekwenyesiri ike ka ị jiri nke a.",
- contain: 'nwere',
- continue: "Gaa n'ihu",
- copy: 'Detuo',
- copy_link: "Detuo njikọ",
- copying: "n'ide",
- copying_file: "n'ide %%",
- cover: 'Mkpuchi',
- create_account: "Mepe akaụntụ",
- create_free_account: "Mepụta Akaụntụ efu",
- create_shortcut: "Mepụta Ụzọ mkpirisi",
- credits: "Ebe e si nweta",
- current_password: "paswọọdụ Ugbu a",
- cut: 'Bee',
- clock: "Elekere",
- clock_visible_hide: 'Ezo - ezoro ezo mgbe niile',
- clock_visible_show: 'Gosi - A na-ahụ ya mgbe niile',
- clock_visible_auto: 'Nchekwa onwe - Emepụtara, a na-ahụ ya naanị na ọnọdụ ihuenyo zuru oke.',
- close_all: 'Mechie ha niile',
- created: 'kere',
- date_modified: 'Ụbọchị ịgbanwe',
- default: 'Default',
- delete: 'Hichapụ',
- delete_account: "Hichapụ Akaụntụ",
- delete_permanently: "Hichapụ kpamkpam",
- deleting_file: "ihichapụ %%",
- deploy_as_app: 'Bugharịa dị ka ngwa',
- descending: 'agbadata',
- desktop: 'desktọpụ',
- desktop_background_fit: "dabara",
- developers: "Ndị mme ya",
- dir_published_as_website: `%strong% e bipụtara ya:`,
- disable_2fa: 'Gbanyụọ 2FA',
- disable_2fa_confirm: "Ị ji n'aka na ịchọrọ gbanyụọ 2FA?",
- disable_2fa_instructions: "Tinye paswọọdụ gị i ji gbanyụọ 2FA.",
- disassociate_dir: "hapụ Akwụkwọ ndekọ",
- documents: 'akwụkwọ',
- dont_allow: 'Ekwela',
- download: 'Budata',
- download_file: 'Budata faịlụ',
- downloading: "Nbudata",
- email: "Email",
- email_change_confirmation_sent: "ezipula email nkwenye na adreesị ozi-e ọhụrụ gị. Biko lelee igbe mbata gị ma soro ntuziaka ka ịmechaa usoro ahụ.",
- email_invalid: 'Email adịghị mma.',
- email_or_username: "Email ma ọ bụ aha njirimara",
- email_required: 'Email bu ihe achọrọ.',
- empty_trash: 'Mkpofu ahịhịa',
- empty_trash_confirmation: `Ị ji n'aka na ịchọrọ ihichapụ ihe ndị dị na ahịhịa?`,
- emptying_trash: 'Mkpofu ahịhịa…',
- enable_2fa: 'Kwado 2FA',
- end_hard: "Kwụsị ike",
- end_process_force_confirm: "O doro gị anya na ịchọrọ ịmanye-akwụsị usoro a?",
- end_soft: "Kwụsị nwayọọ",
- enlarged_qr_code: "gbasawanyere QR Koodu",
- enter_password_to_confirm_delete_user: "Tinye paswọọdụ gị iji kwado nhichapụ akaụntụ gị",
- error_message_is_missing: "Ozi mmebe na-efu.",
- error_unknown_cause: "Nhe amaghị ama mere.",
- error_uploading_files: "Ibulite faịlụ agaghị",
- favorites: "ọkacha mmasị",
- feedback: "nzaghachi",
- feedback_c2a: "Biko jiri fọm dị n'okpuru zitere anyị nzaghachi gị, nkwupụta gị na mkpesa ahụhụ.",
- feedback_sent_confirmation: "Daalụ maka ịkpọtụrụ anyị. Ọ bụrụ na ị nwere email metụtara akaụntụ gị, ị ga-anụghachi anyị ozugbo enwere ike.",
- fit: "dabara",
- folder: 'nchekwa',
- force_quit: 'ịkwụsị ike',
- forgot_pass_c2a: "Chefuru paswọọdụ?",
- from: "si",
- general: "Izugbe",
- get_a_copy_of_on_puter: `Nweta otu '%%' na Puter.com!`,
- get_copy_link: 'Nweta njikọ nke',
- hide_all_windows: "Ezo Windows niile",
- home: 'ụlọ',
- html_document: 'akwụkwọ HTML',
- hue: 'Hue',
- image: 'Onyonyo',
- incorrect_password: "paswọọdụ ezighi ezi",
- invite_link: "Njikọ ịkpọ òkù",
- item: 'ihe',
- items_in_trash_cannot_be_renamed: `Enweghị ike ịnyegharị ihe a aha n'ihi na ọ nọ na ahịhịa. Iji nyegharịa ihe a aha, buru ụzọ dọrọ ya na ahịhịa.`,
- jpeg_image: 'Foto JPEG',
- keep_in_taskbar: 'Debe na Taskbar',
- language: "Asụsụ",
- license: "ikike",
- lightness: 'ìhè',
- link_copied: "depụtagha njikọ",
- loading: 'Na-ebugo ibu',
- log_in: "Banye",
- log_into_another_account_anyway: 'Banye na akaụntụ ọzọ na agbanyeghị',
- log_out: 'pụọ',
- looks_good: "Ọ mara mma!",
- manage_sessions: "Jikwaa Oge",
- menubar_style: "Ụdị Menubar",
- menubar_style_desktop: "Desktọpụ",
- menubar_style_system: "Sistemu",
- menubar_style_window: "Window",
- modified: 'gbanwee',
- move: 'Bugharịa',
- moving_file: "Na Bugharịa %%",
- my_websites: "Weebụsaịtị m",
- name: 'Aha',
- name_cannot_be_empty: 'Aha enweghị ike ịbụ ihe efu.',
- name_cannot_contain_double_period: "Aha enweghị ike ịbụ agwa '..'.",
- name_cannot_contain_period: "Aha enweghị ike ịbụ agwa '.'.",
- name_cannot_contain_slash: "Aha enweghị ike ịnwe agwa '/'.",
- name_must_be_string: "Aha nwere ike ịbụ naanị mkpụrụokwu.",
- name_too_long: `Aha enweghị ike kari %% mkpụrụedemede.`,
- new: 'Ọhụrụ',
- new_email: 'Email Ọhụrụ',
- new_folder: 'nchekwa ọhụrụ',
- new_password: "paswọọdụ ọhụrụ",
- new_username: "Aha ọhụrụ njirimara",
- no: 'Mba',
- no_dir_associated_with_site: 'O nweghị akwụkwọ ndekọ aha jikọtara ya na adreesị a.',
- no_websites_published: "Ị bipụtabeghị webụsaịtị ọ bụla.",
- ok: 'OK',
- open: "Mepee",
- open_in_new_tab: "Mepee na Tab ọhụrụ",
- open_in_new_window: "Mepee na window ọhụrụ",
- open_with: "Ji Mepee Ya",
- original_name: 'Aha izizi',
- original_path: 'Ụzọ izizi',
- oss_code_and_content: "Ngwanrọ na ọdịnaya mepere emepe",
- password: "paswọọdụ",
- password_changed: "gbanwere paswọọdụ.",
- password_recovery_rate_limit: "Ị ruru oke ọnụ ahịa anyị; biko chere nkeji ole na ole. Iji gbochie nke a n'ọdịnihu, zere ibugharị ibe ahụ ọtụtụ oge.",
- password_recovery_token_invalid: "Ihe mgbake mgbake okwuntughe adịkwaghị irè.",
- password_recovery_unknown_error: "Nhe amaghị ama mere. Biko nwaa ọzọ ma emechaa.",
- password_required: 'Achọrọ paswọọdụ.',
- password_strength_error: "paswọọdụ ga-enwerịrị opekata mpe mkpụrụedemede 8 ma nwee opekata mpe otu mkpụrụedemede ukwu, otu mkpụrụedemede obere, otu nọmba na otu agwa pụrụ iche..",
- passwords_do_not_match: '`paswọọdụ ọhụrụ` na `Kwenye paswọọdụ ọhụrụ` adabaghị.',
- paste: 'tinye',
- paste_into_folder: "Tinye n'ime nchekwa",
- path: 'ụzọ',
- personalization: "Nhazi onwe",
- pick_name_for_website: "Họrọ aha maka weebụsaịtị gị:",
- picture: "Foto",
- pictures: 'Foto',
- plural_suffix: 's',
- powered_by_puter_js: `Kwadoro site na {{link=docs}}Puter.js{{/link}}`,
- preparing: "Na-akwado...",
- preparing_for_upload: "Na-akwado maka bulite...",
- print: 'ebipụta',
- privacy: "Nzuzo",
- proceed_to_login: 'Gaba na nbanye',
- proceed_with_account_deletion: "Gaa n'ihu na ihichapụ akaụntụ",
- process_status_initializing: "Na-amalite",
- process_status_running: "Na-agba ọsọ",
- process_type_app: 'Ngwa',
- process_type_init: 'Init',
- process_type_ui: 'UI',
- properties: "Njirimara",
- public: 'eze',
- publish: "Bipụta",
- publish_as_website: 'Bipụta dị ka webụsaịtị',
- puter_description: `Puter bụ igwe ojii nzuzo nke mbụ iji dobe faịlụ gị, ngwa na egwuregwu gị n'otu ebe echekwara, enwere ike ịnweta ya ebe ọ bụla n'oge ọ bụla..`,
- reading_file: "ọgụgụ %strong%",
- recent: "Na nso nso a",
- recommended: "nwere ike ikwu",
- recover_password: "Weghachite paswọọdụ",
- refer_friends_c2a: "Nweta 1 GB maka enyi ọ bụla mepụtara ma kwado akaụntụ na Puter. Enyi gị ga-enwetakwa 1 GB!",
- refer_friends_social_media_c2a: `Nweta 1 GB nke nchekwa efu na Puter.com!`,
- refresh: 'Weghachite ume',
- release_address_confirmation: `O doro gị anya na ịchọrọ wepụtara adreesị a?`,
- remove_from_taskbar:'Wepu na Taskbar',
- rename: 'Nyegharịa aha',
- repeat: 'megharịa',
- replace: 'Dochie',
- replace_all: 'Dochie ihe niile',
- resend_confirmation_code: "Tinyegharịa koodu nkwenye",
- reset_colors: "Tọgharịa Agba",
- restart_puter_confirm: "Ị ji n'aka na ịchọrọ ịmalitegharịa Puter?",
- restore: "weghachi",
- save: 'chekwa',
- saturation: 'juputa',
- save_account: 'Chekwa akaụntụ',
- save_account_to_get_copy_link: "Biko mepụta akaụntụ iji gaa n'ihu.",
- save_account_to_publish: "Biko mepụta akaụntụ iji gaa n'ihu.",
- save_session: 'Chekwa oge',
- save_session_c2a: "Mepụta akaụntụ iji chekwaa nnọkọ gị ugbu a ma zere ịla n'iyi ọrụ gị.",
- scan_qr_c2a: "Chọgharịa koodu dị n'okpuru ka ịbanye na nnọkọ a site na ngwaọrụ ndị ọzọ",
- scan_qr_2fa: 'Jiri ngwa nyocha gị nyochaa QR koodu',
- scan_qr_generic: 'Jiri ekwentị gị ma ọ bụ ngwaọrụ ọzọ nyochaa QR koodu a',
- search: 'Chọọ',
- seconds: 'sekọnd',
- security: "nche",
- select: "Họrọ",
- selected: 'họrọ',
- select_color: 'Họrọ agba…',
- sessions: "Oge",
- send: "Ziga",
- send_password_recovery_email: "Zipu ozi-e mgbake paswọọdụ ",
- session_saved: "Daalụ maka ịmepụta akaụntụ. Achekwala nnọkọ a.",
- settings: "Ntọala",
- set_new_password: "Tinye paswọọdụ ọhụrụ",
- share: "ike",
- share_to: "ike nye",
- share_with: "ji ike nye:",
- shortcut_to: "Ụzọ mkpirisi ka",
- show_all_windows: "Gosi Windows niile",
- show_hidden: 'Gosi ihe ezozo',
- sign_in_with_puter: "Jiri Puter banye",
- sign_up: "Debanye aha",
- signing_in: "Ịbanye…",
- size: 'Nha',
- skip: 'Mafee',
- something_went_wrong: "Ọ nwere ihe adịghị mma.",
- sort_by: 'Hazie site na',
- start: 'mbido',
- status: "Ọnọdụ",
- storage_usage: "Ojiji Nchekwa",
- storage_puter_used: 'nke Puter na-ji',
- taking_longer_than_usual: 'Na-ewe obere oge karịa ka ọ dị na mbụ. Biko chere...',
- task_manager: "Onye njikwa ọrụ",
- taskmgr_header_name: "Aha",
- taskmgr_header_status: "Ọnọdụ",
- taskmgr_header_type: "Ụdị",
- terms: "Usoro",
- text_document: 'Akwụkwọ ederede',
- tos_fineprint: `Site na ịpị 'Mepụta Akaụntụ efu' ị kwenyere na Puter {{link=terms}} Usoro ọrụ{{/link}} na {{link=privacy}}Amụma nzuzo{{/link}} Puter.`,
- transparency: "nghọta",
- trash: 'ahịhịa',
- two_factor: 'Nyocha ihe abụọ',
- two_factor_disabled: 'Agbanyụrụ 2FA',
- two_factor_enabled: 'Agbanyere 2FA',
- type: 'Ụdị',
- type_confirm_to_delete_account: "Pịnye 'kwenye' ka ihichapụ akaụntụ gị.",
- ui_colors: "Agba UI",
- ui_manage_sessions: "Onye njikwa oge",
- ui_revoke: "Kagbuo",
- undo: 'Megharịa',
- unlimited: 'Enweghị oke',
- unzip: "Wepụ ya",
- upload: 'Bulite',
- upload_here: 'Bulite ebe a',
- usage: 'Ojiji',
- username: "Aha njirimara",
- username_changed: 'Emelitere aha njirimara nke ọma.',
- username_required: 'Achọrọ aha njirimara.',
- versions: "Ụdịdị",
- videos: 'Vidiyo',
- visibility: 'Nhụta',
- yes: 'Ee',
- yes_release_it: 'Ee, Hapụ ya',
- you_have_been_referred_to_puter_by_a_friend: "Otu enyi zigara gị na Puter!",
- zip: "Zip",
- zipping_file: "Zipping %strong%",
+ name: "Igbo",
+ english_name: "Igbo",
+ code: "ig",
+ dictionary: {
+ about: "banyere",
+ account: "akaụntụ",
+ account_password: "nyochaa paswọọdụ akaụntụ",
+ access_granted_to: "Enyere ohere",
+ add_existing_account: "Tinye Akaụntụ dị adị",
+ all_fields_required: "A chọrọ mpaghara niile.",
+ allow: "ekwe",
+ apply: "Tinye",
+ ascending: "Na-arịgo",
+ associated_websites: "Weebụsaịtị Ejikọtara",
+ auto_arrange: "ndokwa onwe",
+ background: "ndabere",
+ browse: "Chọgharịa",
+ cancel: "Kagbuo",
+ center: "etiti",
+ change_desktop_background: "ịgbanwe ndabere desktọpụ…",
+ change_email: "ịgbanwe email",
+ change_language: "ịgbanwe Asụsụ",
+ change_password: "ịgbanwe paswọọdụ",
+ change_ui_colors: "ịgbanwe agba UI",
+ change_username: "ịgbanwe aha njirimara",
+ close: "mmechi",
+ close_all_windows: "mmechi Windows niile",
+ close_all_windows_confirm: "Ị ji n'aka na ị chọrọ imechi windo niile?",
+ close_all_windows_and_log_out: "mmechi Windows na wee pụọ",
+ change_always_open_with: "Ị chọrọ I na mepee ụdị faịlụ site na",
+ color: "Agba",
+ confirm: "gosi",
+ confirm_2fa_setup: "Etinyela m koodu ahụ na ngwa nyocha m",
+ confirm_2fa_recovery: "Echekwala m koodu mgbake m na ebe echedoro",
+ hue: "Hue",
+ confirm_account_for_free_referral_storage_c2a:
+ "Mepụta akaụntụ wee kwado adreesị ozi-e gị iji nweta 1 GB nke nchekwa efu. Enyi gị ga-enwetakwa 1 GB nke nchekwa efu.",
+ confirm_code_generic_incorrect: "Koodu ezighi ezi.",
+ confirm_code_generic_too_many_requests:
+ "Ọtụtụ ọchịchọ. Biko chere nkeji ole na ole.",
+ confirm_code_generic_submit: "Nye koodu",
+ confirm_code_generic_try_again: "Nwa ọzọ",
+ confirm_code_generic_title: "Tinye koodu Nkwenye",
+ confirm_code_2fa_instruction:
+ "Tinye koodu ọnụọgụ isi site na ngwa nyocha gị.",
+ confirm_code_2fa_submit_btn: "tinye",
+ confirm_code_2fa_title: "Tinye koodu 2FA",
+ confirm_delete_multiple_items:
+ "Ị ji n'aka na ịchọrọ ihichapụ ihe ndị a kpamkpam?",
+ confirm_delete_single_item: "Ịchọrọ ihichapụ ihe a kpamkpam?",
+ confirm_open_apps_log_out:
+ "Ị nwere ngwa mepere emepe. Ị ji n'aka na ị chọrọ ịpụ?",
+ confirm_new_password: "Kwenye paswọọdụ ọhụrụ",
+ confirm_delete_user:
+ "Ị ji n'aka na ịchọrọ ihichapụ akaụntụ gị? A ga-ehichapụ faịlụ gị niile na data gị kpamkpam. Enweghị ike imegharị ihe a.",
+ confirm_delete_user_title: "Hichapụ Akaụntụ?",
+ confirm_session_revoke: "O doro gị anya na ịchọrọ kagbuo nnọkọ a?",
+ confirm_your_email_address: "Kwenye na adreesị email",
+ contact_us: "Kpọtụrụ anyị",
+ contact_us_verification_required:
+ "Ị ga-enwerịrị adreesị email ekwenyesiri ike ka ị jiri nke a.",
+ contain: "nwere",
+ continue: "Gaa n'ihu",
+ copy: "Detuo",
+ copy_link: "Detuo njikọ",
+ copying: "n'ide",
+ copying_file: "n'ide %%",
+ cover: "Mkpuchi",
+ create_account: "Mepe akaụntụ",
+ create_free_account: "Mepụta Akaụntụ efu",
+ create_shortcut: "Mepụta Ụzọ mkpirisi",
+ credits: "Ebe e si nweta",
+ current_password: "paswọọdụ Ugbu a",
+ cut: "Bee",
+ clock: "Elekere",
+ clock_visible_hide: "Ezo - ezoro ezo mgbe niile",
+ clock_visible_show: "Gosi - A na-ahụ ya mgbe niile",
+ clock_visible_auto:
+ "Nchekwa onwe - Emepụtara, a na-ahụ ya naanị na ọnọdụ ihuenyo zuru oke.",
+ close_all: "Mechie ha niile",
+ created: "kere",
+ date_modified: "Ụbọchị ịgbanwe",
+ default: "Default",
+ delete: "Hichapụ",
+ delete_account: "Hichapụ Akaụntụ",
+ delete_permanently: "Hichapụ kpamkpam",
+ deleting_file: "ihichapụ %%",
+ deploy_as_app: "Bugharịa dị ka ngwa",
+ descending: "agbadata",
+ desktop: "desktọpụ",
+ desktop_background_fit: "dabara",
+ developers: "Ndị mme ya",
+ dir_published_as_website: `%strong% e bipụtara ya:`,
+ disable_2fa: "Gbanyụọ 2FA",
+ disable_2fa_confirm: "Ị ji n'aka na ịchọrọ gbanyụọ 2FA?",
+ disable_2fa_instructions: "Tinye paswọọdụ gị i ji gbanyụọ 2FA.",
+ disassociate_dir: "hapụ Akwụkwọ ndekọ",
+ documents: "akwụkwọ",
+ dont_allow: "Ekwela",
+ download: "Budata",
+ download_file: "Budata faịlụ",
+ downloading: "Nbudata",
+ email: "Email",
+ email_change_confirmation_sent:
+ "ezipula email nkwenye na adreesị ozi-e ọhụrụ gị. Biko lelee igbe mbata gị ma soro ntuziaka ka ịmechaa usoro ahụ.",
+ email_invalid: "Email adịghị mma.",
+ email_or_username: "Email ma ọ bụ aha njirimara",
+ email_required: "Email bu ihe achọrọ.",
+ empty_trash: "Mkpofu ahịhịa",
+ empty_trash_confirmation: `Ị ji n'aka na ịchọrọ ihichapụ ihe ndị dị na ahịhịa?`,
+ emptying_trash: "Mkpofu ahịhịa…",
+ enable_2fa: "Kwado 2FA",
+ end_hard: "Kwụsị ike",
+ end_process_force_confirm:
+ "O doro gị anya na ịchọrọ ịmanye-akwụsị usoro a?",
+ end_soft: "Kwụsị nwayọọ",
+ enlarged_qr_code: "gbasawanyere QR Koodu",
+ enter_password_to_confirm_delete_user:
+ "Tinye paswọọdụ gị iji kwado nhichapụ akaụntụ gị",
+ error_message_is_missing: "Ozi mmebe na-efu.",
+ error_unknown_cause: "Nhe amaghị ama mere.",
+ error_uploading_files: "Ibulite faịlụ agaghị",
+ favorites: "ọkacha mmasị",
+ feedback: "nzaghachi",
+ feedback_c2a:
+ "Biko jiri fọm dị n'okpuru zitere anyị nzaghachi gị, nkwupụta gị na mkpesa ahụhụ.",
+ feedback_sent_confirmation:
+ "Daalụ maka ịkpọtụrụ anyị. Ọ bụrụ na ị nwere email metụtara akaụntụ gị, ị ga-anụghachi anyị ozugbo enwere ike.",
+ fit: "dabara",
+ folder: "nchekwa",
+ force_quit: "ịkwụsị ike",
+ forgot_pass_c2a: "Chefuru paswọọdụ?",
+ from: "si",
+ general: "Izugbe",
+ get_a_copy_of_on_puter: `Nweta otu '%%' na Puter.com!`,
+ get_copy_link: "Nweta njikọ nke",
+ hide_all_windows: "Ezo Windows niile",
+ home: "ụlọ",
+ html_document: "akwụkwọ HTML",
+ hue: "Hue",
+ image: "Onyonyo",
+ incorrect_password: "paswọọdụ ezighi ezi",
+ invite_link: "Njikọ ịkpọ òkù",
+ item: "ihe",
+ items_in_trash_cannot_be_renamed: `Enweghị ike ịnyegharị ihe a aha n'ihi na ọ nọ na ahịhịa. Iji nyegharịa ihe a aha, buru ụzọ dọrọ ya na ahịhịa.`,
+ jpeg_image: "Foto JPEG",
+ keep_in_taskbar: "Debe na Taskbar",
+ language: "Asụsụ",
+ license: "ikike",
+ lightness: "ìhè",
+ link_copied: "depụtagha njikọ",
+ loading: "Na-ebugo ibu",
+ log_in: "Banye",
+ log_into_another_account_anyway: "Banye na akaụntụ ọzọ na agbanyeghị",
+ log_out: "pụọ",
+ looks_good: "Ọ mara mma!",
+ manage_sessions: "Jikwaa Oge",
+ menubar_style: "Ụdị Menubar",
+ menubar_style_desktop: "Desktọpụ",
+ menubar_style_system: "Sistemu",
+ menubar_style_window: "Window",
+ modified: "gbanwee",
+ move: "Bugharịa",
+ moving_file: "Na Bugharịa %%",
+ my_websites: "Weebụsaịtị m",
+ name: "Aha",
+ name_cannot_be_empty: "Aha enweghị ike ịbụ ihe efu.",
+ name_cannot_contain_double_period: "Aha enweghị ike ịbụ agwa '..'.",
+ name_cannot_contain_period: "Aha enweghị ike ịbụ agwa '.'.",
+ name_cannot_contain_slash: "Aha enweghị ike ịnwe agwa '/'.",
+ name_must_be_string: "Aha nwere ike ịbụ naanị mkpụrụokwu.",
+ name_too_long: `Aha enweghị ike kari %% mkpụrụedemede.`,
+ new: "Ọhụrụ",
+ new_email: "Email Ọhụrụ",
+ new_folder: "nchekwa ọhụrụ",
+ new_password: "paswọọdụ ọhụrụ",
+ new_username: "Aha ọhụrụ njirimara",
+ no: "Mba",
+ no_dir_associated_with_site:
+ "O nweghị akwụkwọ ndekọ aha jikọtara ya na adreesị a.",
+ no_websites_published: "Ị bipụtabeghị webụsaịtị ọ bụla.",
+ ok: "OK",
+ open: "Mepee",
+ open_in_new_tab: "Mepee na Tab ọhụrụ",
+ open_in_new_window: "Mepee na window ọhụrụ",
+ open_with: "Ji Mepee Ya",
+ original_name: "Aha izizi",
+ original_path: "Ụzọ izizi",
+ oss_code_and_content: "Ngwanrọ na ọdịnaya mepere emepe",
+ password: "paswọọdụ",
+ password_changed: "gbanwere paswọọdụ.",
+ password_recovery_rate_limit:
+ "Ị ruru oke ọnụ ahịa anyị; biko chere nkeji ole na ole. Iji gbochie nke a n'ọdịnihu, zere ibugharị ibe ahụ ọtụtụ oge.",
+ password_recovery_token_invalid:
+ "Ihe mgbake mgbake okwuntughe adịkwaghị irè.",
+ password_recovery_unknown_error:
+ "Nhe amaghị ama mere. Biko nwaa ọzọ ma emechaa.",
+ password_required: "Achọrọ paswọọdụ.",
+ password_strength_error:
+ "paswọọdụ ga-enwerịrị opekata mpe mkpụrụedemede 8 ma nwee opekata mpe otu mkpụrụedemede ukwu, otu mkpụrụedemede obere, otu nọmba na otu agwa pụrụ iche..",
+ passwords_do_not_match:
+ "`paswọọdụ ọhụrụ` na `Kwenye paswọọdụ ọhụrụ` adabaghị.",
+ paste: "tinye",
+ paste_into_folder: "Tinye n'ime nchekwa",
+ path: "ụzọ",
+ personalization: "Nhazi onwe",
+ pick_name_for_website: "Họrọ aha maka weebụsaịtị gị:",
+ picture: "Foto",
+ pictures: "Foto",
+ plural_suffix: "s",
+ powered_by_puter_js: `Kwadoro site na {{link=docs}}Puter.js{{/link}}`,
+ preparing: "Na-akwado...",
+ preparing_for_upload: "Na-akwado maka bulite...",
+ print: "ebipụta",
+ privacy: "Nzuzo",
+ proceed_to_login: "Gaba na nbanye",
+ proceed_with_account_deletion: "Gaa n'ihu na ihichapụ akaụntụ",
+ process_status_initializing: "Na-amalite",
+ process_status_running: "Na-agba ọsọ",
+ process_type_app: "Ngwa",
+ process_type_init: "Init",
+ process_type_ui: "UI",
+ properties: "Njirimara",
+ public: "eze",
+ publish: "Bipụta",
+ publish_as_website: "Bipụta dị ka webụsaịtị",
+ puter_description: `Puter bụ igwe ojii nzuzo nke mbụ iji dobe faịlụ gị, ngwa na egwuregwu gị n'otu ebe echekwara, enwere ike ịnweta ya ebe ọ bụla n'oge ọ bụla..`,
+ reading_file: "ọgụgụ %strong%",
+ recent: "Na nso nso a",
+ recommended: "nwere ike ikwu",
+ recover_password: "Weghachite paswọọdụ",
+ refer_friends_c2a:
+ "Nweta 1 GB maka enyi ọ bụla mepụtara ma kwado akaụntụ na Puter. Enyi gị ga-enwetakwa 1 GB!",
+ refer_friends_social_media_c2a: `Nweta 1 GB nke nchekwa efu na Puter.com!`,
+ refresh: "Weghachite ume",
+ release_address_confirmation: `O doro gị anya na ịchọrọ wepụtara adreesị a?`,
+ remove_from_taskbar: "Wepu na Taskbar",
+ rename: "Nyegharịa aha",
+ repeat: "megharịa",
+ replace: "Dochie",
+ replace_all: "Dochie ihe niile",
+ resend_confirmation_code: "Tinyegharịa koodu nkwenye",
+ reset_colors: "Tọgharịa Agba",
+ restart_puter_confirm: "Ị ji n'aka na ịchọrọ ịmalitegharịa Puter?",
+ restore: "weghachi",
+ save: "chekwa",
+ saturation: "juputa",
+ save_account: "Chekwa akaụntụ",
+ save_account_to_get_copy_link: "Biko mepụta akaụntụ iji gaa n'ihu.",
+ save_account_to_publish: "Biko mepụta akaụntụ iji gaa n'ihu.",
+ save_session: "Chekwa oge",
+ save_session_c2a:
+ "Mepụta akaụntụ iji chekwaa nnọkọ gị ugbu a ma zere ịla n'iyi ọrụ gị.",
+ scan_qr_c2a:
+ "Chọgharịa koodu dị n'okpuru ka ịbanye na nnọkọ a site na ngwaọrụ ndị ọzọ",
+ scan_qr_2fa: "Jiri ngwa nyocha gị nyochaa QR koodu",
+ scan_qr_generic: "Jiri ekwentị gị ma ọ bụ ngwaọrụ ọzọ nyochaa QR koodu a",
+ search: "Chọọ",
+ seconds: "sekọnd",
+ security: "nche",
+ select: "Họrọ",
+ selected: "họrọ",
+ select_color: "Họrọ agba…",
+ sessions: "Oge",
+ send: "Ziga",
+ send_password_recovery_email: "Zipu ozi-e mgbake paswọọdụ ",
+ session_saved: "Daalụ maka ịmepụta akaụntụ. Achekwala nnọkọ a.",
+ settings: "Ntọala",
+ set_new_password: "Tinye paswọọdụ ọhụrụ",
+ share: "ike",
+ share_to: "ike nye",
+ share_with: "ji ike nye:",
+ shortcut_to: "Ụzọ mkpirisi ka",
+ show_all_windows: "Gosi Windows niile",
+ show_hidden: "Gosi ihe ezozo",
+ sign_in_with_puter: "Jiri Puter banye",
+ sign_up: "Debanye aha",
+ signing_in: "Ịbanye…",
+ size: "Nha",
+ skip: "Mafee",
+ something_went_wrong: "Ọ nwere ihe adịghị mma.",
+ sort_by: "Hazie site na",
+ start: "mbido",
+ status: "Ọnọdụ",
+ storage_usage: "Ojiji Nchekwa",
+ storage_puter_used: "nke Puter na-ji",
+ taking_longer_than_usual:
+ "Na-ewe obere oge karịa ka ọ dị na mbụ. Biko chere...",
+ task_manager: "Onye njikwa ọrụ",
+ taskmgr_header_name: "Aha",
+ taskmgr_header_status: "Ọnọdụ",
+ taskmgr_header_type: "Ụdị",
+ terms: "Usoro",
+ text_document: "Akwụkwọ ederede",
+ tos_fineprint: `Site na ịpị 'Mepụta Akaụntụ efu' ị kwenyere na Puter {{link=terms}} Usoro ọrụ{{/link}} na {{link=privacy}}Amụma nzuzo{{/link}} Puter.`,
+ transparency: "nghọta",
+ trash: "ahịhịa",
+ two_factor: "Nyocha ihe abụọ",
+ two_factor_disabled: "Agbanyụrụ 2FA",
+ two_factor_enabled: "Agbanyere 2FA",
+ type: "Ụdị",
+ type_confirm_to_delete_account: "Pịnye 'kwenye' ka ihichapụ akaụntụ gị.",
+ ui_colors: "Agba UI",
+ ui_manage_sessions: "Onye njikwa oge",
+ ui_revoke: "Kagbuo",
+ undo: "Megharịa",
+ unlimited: "Enweghị oke",
+ unzip: "Wepụ ya",
+ upload: "Bulite",
+ upload_here: "Bulite ebe a",
+ usage: "Ojiji",
+ username: "Aha njirimara",
+ username_changed: "Emelitere aha njirimara nke ọma.",
+ username_required: "Achọrọ aha njirimara.",
+ versions: "Ụdịdị",
+ videos: "Vidiyo",
+ visibility: "Nhụta",
+ yes: "Ee",
+ yes_release_it: "Ee, Hapụ ya",
+ you_have_been_referred_to_puter_by_a_friend: "Otu enyi zigara gị na Puter!",
+ zip: "Zip",
+ zipping_file: "Zipping %strong%",
- // === 2FA Setup ===
- setup2fa_1_step_heading: 'Mepee ngwa nyocha',
- setup2fa_1_instructions: `
+ // === 2FA Setup ===
+ setup2fa_1_step_heading: "Mepee ngwa nyocha",
+ setup2fa_1_instructions: `
Ị nwere ike iji ngwa nyocha ọ bụla na-akwado protocol Paswọdu Otu oge (TOTP) dabere na Oge.
Enwere ọtụtụ nhọrọ, mana ọ bụrụ na ị maghị
Authy
bụ nhọrọ siri ike maka android na iOS.
`,
- setup2fa_2_step_heading: 'Nyochaa QR koodu',
- setup2fa_3_step_heading: 'Tinye koodu ọnụọgụ isi',
- setup2fa_4_step_heading: 'Detuo koodu mgbake gị',
- setup2fa_4_instructions: `
+ setup2fa_2_step_heading: "Nyochaa QR koodu",
+ setup2fa_3_step_heading: "Tinye koodu ọnụọgụ isi",
+ setup2fa_4_step_heading: "Detuo koodu mgbake gị",
+ setup2fa_4_instructions: `
Koodu mgbake ndị a bụ naanị ụzọ ị ga-esi iba akaụntụ gị ma ọ bụrụ na ekwentị gị furu ma ọ bụ enweghị ike iji ngwa nyocha gị..
Gbaa mbọ hụ na ịchekwaa ha n'ebe dị mma.
`,
- setup2fa_5_step_heading: 'Kwenye ntọlite 2FA',
- setup2fa_5_confirmation_1: 'Echekwala m koodu mgbake m na ebe echekwa',
- setup2fa_5_confirmation_2: 'Adị m njikere i Kwado 2FA',
- setup2fa_5_button: 'Kwado 2FA',
+ setup2fa_5_step_heading: "Kwenye ntọlite 2FA",
+ setup2fa_5_confirmation_1: "Echekwala m koodu mgbake m na ebe echekwa",
+ setup2fa_5_confirmation_2: "Adị m njikere i Kwado 2FA",
+ setup2fa_5_button: "Kwado 2FA",
- // === 2FA Login ===
- login2fa_otp_title: 'Tinye koodu 2FA',
- login2fa_otp_instructions: 'Tinye koodu ọnụọgụ isi site na ngwa nyocha.',
- login2fa_recovery_title: 'Tinye koodu mgbake',
- login2fa_recovery_instructions: 'Tinye otu koodu mgbake gị i ji eba akaụntụ gị.',
- login2fa_use_recovery_code: 'Ji koodu mgbake',
- login2fa_recovery_back: 'Azu',
- login2fa_recovery_placeholder: 'XXXXXXXX',
+ // === 2FA Login ===
+ login2fa_otp_title: "Tinye koodu 2FA",
+ login2fa_otp_instructions: "Tinye koodu ọnụọgụ isi site na ngwa nyocha.",
+ login2fa_recovery_title: "Tinye koodu mgbake",
+ login2fa_recovery_instructions:
+ "Tinye otu koodu mgbake gị i ji eba akaụntụ gị.",
+ login2fa_use_recovery_code: "Ji koodu mgbake",
+ login2fa_recovery_back: "Azu",
+ login2fa_recovery_placeholder: "XXXXXXXX",
- "change": "gbanwee",
- "clock_visibility": "Ihe ngosi elekere",
- "reading": "gụgụ %strong%",
- "writing": "Na-ede %strong%",
- "unzipping": "mkpọpu %strong%",
- "sequencing": "Ịtọgharị n’usoro %strong%",
- "zipping": "zipụ %strong%",
- "Editor": "Onye nchịgharị",
- "Viewer": "Onye na-ekiri",
- "People with access": "Ndị nwere ohere",
- "Share With…": "Kekọrịta na",
- "Owner": "Onye nwe",
- "You can't share with yourself.": "Ị nweghị ike ịkekọrịta ya onwe gị.",
- "This user already has access to this item": "Onye a enweela ohere ịbanye ihe a",
- }
+ change: "gbanwee",
+ clock_visibility: "Ihe ngosi elekere",
+ reading: "ogụgụ %strong%",
+ writing: "Na-ede %strong%",
+ unzipping: "na mkpọpu ya %strong%",
+ sequencing: "usoro %strong%",
+ zipping: "zipụ %strong%",
+ Editor: "Onye nchịgharị",
+ Viewer: "Onye na-ekiri",
+ "People with access": "Ndị nwere ohere",
+ "Share With…": "Kekọrịta na",
+ Owner: "Onye nwe",
+ "You can't share with yourself.": "Ị nweghị ike ịkekọrịta ya onwe gị.",
+ "This user already has access to this item":
+ "Onye a enweela ohere ịbanye ihe a",
+
+ "billing.change_payment_method": "Gbanwee", // In English: "Change"
+ "billing.cancel": "Kagbuo", // In English: "Cancel"
+ "billing.download_invoice": "Budata", // In English: "Download"
+ "billing.payment_method": "Ụzọ nkwụnye ụgwọ", // In English: "Payment Method"
+ "billing.payment_method_updated": "Emelitere usoro ịkwụ ụgwọ!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Kwenye usoro ịkwụ ụgwọ", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Akụkọ ịkwụ ụgwọ", // In English: "Payment History"
+ "billing.refunded": "Akwụghachitere", // In English: "Refunded"
+ "billing.paid": "Akwụ ụgwọ", // In English: "Paid"
+ "billing.ok": "Ọ DỊ MMA", // In English: "OK"
+ "billing.resume_subscription": "Malitegharịa ndenye aha", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Akagbuola ndenye aha gị", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description":
+ "Ị ka ga-enwe ike ịnweta ndenye aha gị ruo na njedebe nke oge ịgba ụgwọ a", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "N'efu", // In English: "Free"
+ "billing.offering.pro": "Ọkachamara", // In English: "Professional"
+ "billing.offering.business": "Azụmahịa", // In English: "Business"
+ "billing.cloud_storage": "Nchekwa igwe ojii", // In English: "Cloud Storage"
+ "billing.ai_access": "Nweta AI", // In English: "AI Access"
+ "billing.bandwidth": "Bandwit", // In English: "Bandwidth"
+ "billing.apps_and_games": "Ngwa & Egwuregwu", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Kwalite ka sie ike", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Gbanwee na ike", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Ntọala ịkwụ ụgwọ", // In English: "Payment Setup"
+ "billing.back": "Azu", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Ị debanyere aha na ọkwa siri ike.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Ị debanyere aha ugbu a", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation":
+ "Ị ji n'aka na ịchọrọ ịkagbu ndenye aha gị?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Ntọala ndenye aha", // In English: "Subscription Setup"
+ "billing.cancel_it": "Kagbuo ya", // In English: "Cancel It"
+ "billing.keep_it": "Debe ya", // In English: "Keep It"
+ "billing.subscription_resumed": "Eweghachila ndenye aha siri ike gị!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Kwalite Ugbu a", // In English: "Upgrade Now"
+ "billing.upgrade": "Nweta nkwalite", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Ị nọ ugbu a na atụmatụ efu.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Budata nnata", // In English: "Download Receipt"
+ "billing.subscription_check_error":
+ "Nsogbu mere mgbe ị na-elele ọkwa ndenye aha gị.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed":
+ "Ekwenyeghị email gị. Anyị ga-ezitere gị koodu iji gosi ya ugbu a.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until":
+ "Ị kagbuola ndenye aha gị, ọ ga-agbanyekwa na ọkwa efu na-akpaghị aka na njedebe nke oge ịgba ụgwọ. Agaghị akwụ gị ụgwọ ọzọ ọ gwụla ma ị debanyere aha ọzọ", // 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":
+ "Atụmatụ gị ugbu a ruo ọgwụgwụ nke oge ịgba ụgwọ a.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Atụmatụ ugbu a", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Kagbuo ndenye aha", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "jikwaa", // In English: "Manage"
+ "billing.limited": "Oke", // In English: "Limited"
+ "billing.expanded": "Gbasaa", // In English: "Expanded"
+ "billing.accelerated": "Ọsọ ọsọ", // In English: "Accelerated"
+ "billing.enjoy_msg":
+ "Nwee obi ụtọ na Nchekwa igwe ojii gbakwunyere uru ndị ọzọ", // In English: "Enjoy %% of Cloud Storage plus other benefits."
+ },
};
export default ig;
diff --git a/src/gui/src/i18n/translations/it.js b/src/gui/src/i18n/translations/it.js
index e4842af122..34766bb046 100644
--- a/src/gui/src/i18n/translations/it.js
+++ b/src/gui/src/i18n/translations/it.js
@@ -18,349 +18,437 @@
*/
const it = {
- name: "Italiano",
- english_name: "Italian",
- code: "it",
- dictionary: {
- about: "Informazioni", // This is better in the context of the setting page title. It may be tricky.
- account: "Account",
- account_password: "Verifica Password del account",
- access_granted_to: "Accesso garantito a",
- add_existing_account: "Aggiungi un account esistente",
- all_fields_required: "Tutti i campi sono richiesti.",
- allow: "Consenti",
- apply: "Applica",
- ascending: 'Ascendente',
- associated_websites: "Siti associati",
- auto_arrange: 'Organizzazione automatica',
- background: "Sfondo",
- browse: "Sfoglia",
- cancel: 'Annulla',
- center: 'Centra ',
- change_desktop_background: 'Modifica sfondo…',
- change_email: "Modifica Email",
- change_language: "Cambia lingua",
- change_password: "Modifica password",
- change_ui_colors: "Cambia i colori dell'interfaccia",
- change_username: "Modifica Nome Utente",
- close: "Chiudi",
- close_all_windows: "Chiudi tutte le finestre",
- close_all_windows_confirm: "Sei sicuro di voler chiudere tutte le finestre?",
- close_all_windows_and_log_out: "Chiudi tutte le finestre e disconnettiti",
- change_always_open_with: "Vuoi sempre aprire questo tipo di file con",
- color: 'Colore',
- confirm: "Conferma",
- confirm_2fa_setup: "Ho aggiunto il codice alla mia app di autenticazione",
- confirm_2fa_recovery: "Ho salvato il codice di recupero in un posto sicuro",
- confirm_account_for_free_referral_storage_c2a: 'Crea un account e conferma la tua email per ricevere 1 GB di spazio di archiviazione gratuito. Anche il tuo amico riceverà dello spazio extra!',
- confirm_code_generic_incorrect: "Codice errato.",
- confirm_code_generic_too_many_requests: "Troppe richieste. Attendi qualche minuto.",
- confirm_code_generic_submit: "Invia Codice",
- confirm_code_generic_try_again: "Riprova",
- confirm_code_generic_title: "Inserisci Codice di Conferma",
- confirm_code_2fa_instruction: "Inserisci il codice a 6 cifre dalla tua app di autenticazione.",
- confirm_code_2fa_submit_btn: "Invia",
- confirm_code_2fa_title: "Inserisci il Codice 2FA",
- confirm_delete_multiple_items: 'Sei sicuro di voler eliminare definitivamente questi elementi?',
- confirm_delete_single_item: 'Vuoi eliminare definitivamente questo elemento?',
- confirm_open_apps_log_out: 'Ci sono delle applicazioni aperte. Sei sicuro di voler effettuare il log out?',
- confirm_new_password: "Conferma la nuova Password",
- confirm_delete_user: "Sei sicuro di voler cancellare il tuo account? Tutti i tuoi file e dati saranno definitivamente cancellati. Quest'azione non è reversibile.",
- confirm_delete_user_title: "Cancellare l'Account?",
- confirm_session_revoke: "Sei sicuro di voler revocare questa sessione?",
- confirm_your_email_address: "Conferma il tuo indirizzo email",
- contact_us: "Contattaci",
- contact_us_verification_required: "Devi verificare il tuo indirizzo email per utilizzare questa funzione.",
- contain: 'Contiene',
- continue: "Continua",
- copy: 'Copia',
- copy_link: "Copia il link",
- copying: "Copia in corso",
- copying_file: "Copiando %%",
- cover: 'Cover',
- create_account: "Crea Account",
- create_free_account: "Crea un account gratis",
- create_shortcut: "Crea Scorciatoia",
- credits: "Crediti",
- current_password: "Password attuale",
- cut: 'Taglia',
- clock: "Orologio",
- clock_visible_hide: 'Nascondi - Sempre nascosto',
- clock_visible_show: 'Mostra - Sempre visibile',
- clock_visible_auto: 'Auto - Default, visibile solo in modalità schermo intero',
- close_all: 'Chiudi tutte',
- created: 'Creata',
- date_modified: 'Data ultima modifica',
- default: 'Predefinita',
- delete: 'Elimina',
- delete_account: "Elimina Account",
- delete_permanently: "Elimina permanentemente",
- deleting_file: "Eliminando %%",
- deploy_as_app: 'Distribuisci come Applicazione',
- descending: 'Discendente',
- desktop: 'Scrivania',
- desktop_background_fit: "Adatta",
- developers: "Sviluppatori",
- dir_published_as_website: `%strong% è stato pubblicato su:`,
- disable_2fa: 'Disabilita 2FA',
- disable_2fa_confirm: "Sei sicuro di voler disabilitare la 2FA?",
- disable_2fa_instructions: "Inserisci la tua password per disabilitare la 2FA.",
- disassociate_dir: "Dissocia la Directory",
- documents: 'Documenti',
- dont_allow: 'Non consentire',
- download: 'Scarica',
- download_file: 'Scarica file',
- downloading: "Download in corso",
- email: "Email",
- email_change_confirmation_sent: "Ti abbiamo inviato un'email di conferma. Controlla la tua casella di posta e segui le istruzioni per completare il processo.",
- email_invalid: 'Email invalida.',
- email_or_username: "Email o Nome Utente",
- email_required: "Email richiesta.",
- empty_trash: 'Svuota Cestino',
- empty_trash_confirmation: `Sei sicuro di voler svuotare il cestino?`,
- emptying_trash: 'Il cestino si sta svuotando…',
- enable_2fa: 'Abilita la 2FA',
- end_hard: "Forza la chiusura",
- end_process_force_confirm: "Sei sicuro di voler forzare la chiusura di questo processo?",
- end_soft: "Chiudi",
- enlarged_qr_code: "QR Code ingrandito",
- enter_password_to_confirm_delete_user: "Inserisci la tua password per confermare l'eliminazione del tuo account.",
- error_message_is_missing: "Messaggio di errore mancante.",
- error_unknown_cause: "Errore sconosciuto.",
- error_uploading_files: "Errore durante l'upload dei file.",
- favorites: "Preferiti",
- feedback: "Feedback",
- feedback_c2a: "Usa il form qua sotto per inviarci feedback, commenti, e segnalarci dei bug.",
- feedback_sent_confirmation: "Grazie per averci contattato. Se hai un indirizzo email associato al tuo account, ti ricontatteremo il prima possibile.",
- fit: 'Adatta',
- folder: 'Cartella',
- force_quit: 'Forza la chiusura',
- forgot_pass_c2a: "Password dimenticata?",
- from: "Da",
- general: "Generale",
- get_a_copy_of_on_puter: `Ottieni una copia di '%%' su Puter.com!`,
- get_copy_link: 'Ottieni link di copia',
- hide_all_windows: "Nascondi tutte le finestre",
- home: 'Home',
- html_document: 'Documento HTML',
- hue: 'Tonalità',
- image: 'Immagine',
- incorrect_password: 'Password errata',
- invite_link: "Link d’invito",
- item: 'elemento',
- items_in_trash_cannot_be_renamed: `Impossibile rinominare un elemento nel Cestino. Per rinominarlo, è necessario ripristinarlo.`,
- jpeg_image: 'Immagine JPEG',
- keep_in_taskbar: 'Blocca nella barra delle applicazioni',
- language: "Lingua",
- license: "Licenza",
- lightness: 'Luminosità',
- link_copied: "Link copiato",
- loading: 'Caricamento',
- log_in: "Accedi",
- log_into_another_account_anyway: 'Accedi comunque con un altro account',
- log_out: 'Disconnettiti',
- looks_good: "Sembra buono!",
- manage_sessions: "Gestisci le sessioni",
- menubar_style: "Stile della barra dei menu",
- menubar_style_desktop: "Scrivania",
- menubar_style_system: "Sistema",
- menubar_style_window: "Finestra",
- modified: 'Modificato',
- move: 'Sposta',
- moving_file: "Spostamento in corso %%",
- my_websites: "I miei siti web",
- name: 'Nome',
- name_cannot_be_empty: 'Il nome non può essere vuoto.',
- name_cannot_contain_double_period: "Il nome non può contenere '..' .",
- name_cannot_contain_period: "Il nome non può contenere '.' .",
- name_cannot_contain_slash: "Il nome non può contenere '/' .",
- name_must_be_string: "Il nome può contenere una sola linea.",
- name_too_long: `Il nome non può essere più lungo di %% caratteri.`,
- new: 'Nuovo',
- new_email: "Nuova Email",
- new_folder: 'Nuova Cartella',
- new_password: "Nuova Password",
- new_username: "Nuovo Nome Utente",
- no: 'No',
- no_dir_associated_with_site: 'Nessuna directory è stata associata all’indirizzo.',
- no_websites_published: "Non hai pubblicato nessun sito web. Clicca tasto destro su una cartella per iniziare.",
- ok: 'OK',
- open: "Apri",
- open_in_new_tab: "Apri in una nuova scheda",
- open_in_new_window: "Apri in una nuova finestra",
- open_with: "Apri con",
- original_name: 'Nome originale',
- original_path: 'Percorso originale',
- oss_code_and_content: "Contenuto e software Open Source",
- password: "Password",
- password_changed: "Password modificata.",
- password_recovery_rate_limit: "Hai raggiunto il limite di richieste; per favore attendi qualche minuto. Per evitarlo in futuro evita di ricaricare la pagina troppe volte.",
- password_recovery_token_invalid: "Questo token per il recupero della password non è valido.",
- password_recovery_unknown_error: "Errore sconosciuto. Per favore riprova più tardi.",
- password_required: 'Password richiesta.',
- password_strength_error: "La password deve essere lunga almeno 8 caratteri e contenete almeno una maiuscola, una minuscola, un numero e un carattere speciale.",
- passwords_do_not_match: 'Le caselle `Nuova Password` and `Conferma Nuova Password` non corrispondono.',
- paste: 'Incolla',
- paste_into_folder: "Incolla nella cartella",
- path: 'Percorso',
- personalization: "Personalizzazione",
- pick_name_for_website: "Scegli un nome per il tuo sito web:",
- picture: "Immagine",
- pictures: 'Immagini',
- plural_suffix: 'i',
- powered_by_puter_js: `Realizzato con {{link=docs}}Puter.js{{/link}}`,
- preparing: "Preparazione in corso...",
- preparing_for_upload: "Preparazione per l’upload...",
- print: 'Stampa',
- privacy: "Privacy",
- proceed_to_login: 'Procedi con il login',
- proceed_with_account_deletion: "Continua con l'eliminazione dell'account",
- process_status_initializing: "Inizializzando",
- process_status_running: "In esecuzione",
- process_type_app: 'App',
- process_type_init: 'Inizializzazione',
- process_type_ui: 'UI',
- properties: "Proprietà",
- public: "Pubblico",
- publish: "Pubblica",
- publish_as_website: 'Pubblica come sito web',
- puter_description: `Puter è un cloud personale che mette la privacy al primo posto, per conservare tutti i tuoi file, app e giochi in un unico luogo sicuro, accessibile da qualsiasi luogo e in qualsiasi momento.`,
- reading_file: "Leggendo %strong%",
- recent: "Recenti",
- recommended: "Consigliati",
- recover_password: "Ripristina la Password",
- refer_friends_c2a: "Ottieni 1 GB di spazio di archiviazione per ogni amico che crea un account e conferma l’email su Puter. Anche il tuo amico riceverà dello spazio extra!",
- refer_friends_social_media_c2a: `Ottieni 1GB di spazio di spazio di archiviazione gratuito su Puter.com!`,
- refresh: 'Ricarica',
- release_address_confirmation: `Sei sicuro di voler liberare questo indirizzo?`,
- remove_from_taskbar:'Sblocca dalla barra delle applicazioni',
- rename: 'Rinomina',
- repeat: 'Ripeti',
- replace: 'Sostituisci',
- replace_all: 'Sostituisci tutto',
- resend_confirmation_code: "Invia di nuovo il codice di conferma",
- reset_colors: "Ripristina i colori",
- restart_puter_confirm: "Sei sicuro di voler riavviare Puter?",
- restore: "Ripristina",
- save: "Salva",
- saturation: "Saturazione",
- save_account: 'Salva Account',
- save_account_to_get_copy_link: "È necessario creare un account per procedere.",
- save_account_to_publish: 'È necessario creare un account per procedere.',
- save_session: "Salva sessione",
- save_session_c2a: 'Crea un account per salvare la tua sessione e non perdere i tuoi dati.',
- scan_qr_c2a: 'Scansiona il codice qua sotto per utilizzare questa sessione da altri dispositivi',
- scan_qr_2fa: 'Scansiona il codice QR con la tua app di autenticazione',
- scan_qr_generic: 'Scansiona il codice QR usando il tuo smartphone',
- search: 'Search',
- seconds: 'seconds',
- security: "Sicurezza",
- select: "Seleziona",
- selected: 'Selezionato',
- select_color: 'Seleziona un colore…',
- sessions: "Sessioni",
- send: "Invia",
- send_password_recovery_email: "Invia email per il ripristino della password",
- session_saved: "Grazie per aver creato un account. La sessione è stata salvata",
- settings: "Impostazioni",
- set_new_password: "Imposta una nuova Password",
- share: "Condividi",
- share_to: "Condividi con",
- share_with: "Condividi con",
- shortcut_to: "Scorciatoia per",
- show_all_windows: "Mostra tutte le finestre",
- show_hidden: 'Mostra nascosti',
- sign_in_with_puter: "Accedi con Puter",
- sign_up: "Registrati",
- signing_in: "Accesso in corso…",
- size: 'Dimensione',
- skip: "Salta",
- something_went_wrong: "Qualcosa è andato storto.",
- sort_by: 'Ordina per',
- start: 'Start',
- status: 'Stato',
- storage_usage: "Utilizzo dello spazio",
- storage_puter_used: 'utilizzato da Puter',
- taking_longer_than_usual: 'Il processo in corso ci sta mettendo più del solito. Attendere prego...',
- task_manager: "Gestione attività",
- taskmgr_header_name: "Nome",
- taskmgr_header_status: "Stato",
- taskmgr_header_type: "Tipo",
- terms: "Termini",
- text_document: 'Documento di testo',
- tos_fineprint: `Cliccando su 'Crea un account gratis' accetti i {{link=terms}}Termini di Servizio{{/link}} e l'{{link=privacy}}Informativa sulla Privacy{{/link}} di Puter.`,
- transparency: "Trasparenza",
- trash: 'Cestino',
- two_factor: 'Autenticazione a due fattori',
- two_factor_disabled: '2FA Disabilitata',
- two_factor_enabled: '2FA Abilitata',
- type: 'Tipo',
- type_confirm_to_delete_account: "Scrivi 'conferma' per eliminare il tuo account.",
- ui_colors: "Colori dell'interfaccia",
- ui_manage_sessions: "Session Manager",
- ui_revoke: "Revoca",
- undo: 'Annulla',
- unlimited: 'Illimitato',
- unzip: "Estrai",
- upload: 'Carica',
- upload_here: 'Carica qui',
- usage: 'Utilizzo',
- username: "Nome Utente",
- username_changed: 'Nome utente aggiornato con successo.',
- username_required: 'Il nome utente è richiesto.',
- versions: "Versioni",
- videos: "Video",
- visibility: "Visibilità",
- yes: 'Sì',
- yes_release_it: 'Si, rilascialo',
- you_have_been_referred_to_puter_by_a_friend: "Sei stato invitato su Puter da un amico!",
- zip: "File compresso",
- zipping_file: "Compressione di %strong%",
+ name: "Italiano",
+ english_name: "Italian",
+ code: "it",
+ dictionary: {
+ about: "Informazioni", // This is better in the context of the setting page title. It may be tricky.
+ account: "Account",
+ account_password: "Verifica Password del account",
+ access_granted_to: "Accesso garantito a",
+ add_existing_account: "Aggiungi un account esistente",
+ all_fields_required: "Tutti i campi sono richiesti.",
+ allow: "Consenti",
+ apply: "Applica",
+ ascending: "Ascendente",
+ associated_websites: "Siti associati",
+ auto_arrange: "Organizzazione automatica",
+ background: "Sfondo",
+ browse: "Sfoglia",
+ cancel: "Annulla",
+ center: "Centra ",
+ change_desktop_background: "Modifica sfondo…",
+ change_email: "Modifica Email",
+ change_language: "Cambia lingua",
+ change_password: "Modifica password",
+ change_ui_colors: "Cambia i colori dell'interfaccia",
+ change_username: "Modifica Nome Utente",
+ close: "Chiudi",
+ close_all_windows: "Chiudi tutte le finestre",
+ close_all_windows_confirm:
+ "Sei sicuro di voler chiudere tutte le finestre?",
+ close_all_windows_and_log_out: "Chiudi tutte le finestre e disconnettiti",
+ change_always_open_with: "Vuoi sempre aprire questo tipo di file con",
+ color: "Colore",
+ confirm: "Conferma",
+ confirm_2fa_setup: "Ho aggiunto il codice alla mia app di autenticazione",
+ confirm_2fa_recovery: "Ho salvato il codice di recupero in un posto sicuro",
+ confirm_account_for_free_referral_storage_c2a:
+ "Crea un account e conferma la tua email per ricevere 1 GB di spazio di archiviazione gratuito. Anche il tuo amico riceverà dello spazio extra!",
+ confirm_code_generic_incorrect: "Codice errato.",
+ confirm_code_generic_too_many_requests:
+ "Troppe richieste. Attendi qualche minuto.",
+ confirm_code_generic_submit: "Invia Codice",
+ confirm_code_generic_try_again: "Riprova",
+ confirm_code_generic_title: "Inserisci Codice di Conferma",
+ confirm_code_2fa_instruction:
+ "Inserisci il codice a 6 cifre dalla tua app di autenticazione.",
+ confirm_code_2fa_submit_btn: "Invia",
+ confirm_code_2fa_title: "Inserisci il Codice 2FA",
+ confirm_delete_multiple_items:
+ "Sei sicuro di voler eliminare definitivamente questi elementi?",
+ confirm_delete_single_item:
+ "Vuoi eliminare definitivamente questo elemento?",
+ confirm_open_apps_log_out:
+ "Ci sono delle applicazioni aperte. Sei sicuro di voler effettuare il log out?",
+ confirm_new_password: "Conferma la nuova Password",
+ confirm_delete_user:
+ "Sei sicuro di voler cancellare il tuo account? Tutti i tuoi file e dati saranno definitivamente cancellati. Quest'azione non è reversibile.",
+ confirm_delete_user_title: "Cancellare l'Account?",
+ confirm_session_revoke: "Sei sicuro di voler revocare questa sessione?",
+ confirm_your_email_address: "Conferma il tuo indirizzo email",
+ contact_us: "Contattaci",
+ contact_us_verification_required:
+ "Devi verificare il tuo indirizzo email per utilizzare questa funzione.",
+ contain: "Contiene",
+ continue: "Continua",
+ copy: "Copia",
+ copy_link: "Copia il link",
+ copying: "Copia in corso",
+ copying_file: "Copiando %%",
+ cover: "Cover",
+ create_account: "Crea Account",
+ create_free_account: "Crea un account gratis",
+ create_shortcut: "Crea Scorciatoia",
+ credits: "Crediti",
+ current_password: "Password attuale",
+ cut: "Taglia",
+ clock: "Orologio",
+ clock_visible_hide: "Nascondi - Sempre nascosto",
+ clock_visible_show: "Mostra - Sempre visibile",
+ clock_visible_auto:
+ "Auto - Default, visibile solo in modalità schermo intero",
+ close_all: "Chiudi tutte",
+ created: "Creata",
+ date_modified: "Data ultima modifica",
+ default: "Predefinita",
+ delete: "Elimina",
+ delete_account: "Elimina Account",
+ delete_permanently: "Elimina permanentemente",
+ deleting_file: "Eliminando %%",
+ deploy_as_app: "Distribuisci come Applicazione",
+ descending: "Discendente",
+ desktop: "Scrivania",
+ desktop_background_fit: "Adatta",
+ developers: "Sviluppatori",
+ dir_published_as_website: `%strong% è stato pubblicato su:`,
+ disable_2fa: "Disabilita 2FA",
+ disable_2fa_confirm: "Sei sicuro di voler disabilitare la 2FA?",
+ disable_2fa_instructions:
+ "Inserisci la tua password per disabilitare la 2FA.",
+ disassociate_dir: "Dissocia la Directory",
+ documents: "Documenti",
+ dont_allow: "Non consentire",
+ download: "Scarica",
+ download_file: "Scarica file",
+ downloading: "Download in corso",
+ email: "Email",
+ email_change_confirmation_sent:
+ "Ti abbiamo inviato un'email di conferma. Controlla la tua casella di posta e segui le istruzioni per completare il processo.",
+ email_invalid: "Email invalida.",
+ email_or_username: "Email o Nome Utente",
+ email_required: "Email richiesta.",
+ empty_trash: "Svuota Cestino",
+ empty_trash_confirmation: `Sei sicuro di voler svuotare il cestino?`,
+ emptying_trash: "Il cestino si sta svuotando…",
+ enable_2fa: "Abilita la 2FA",
+ end_hard: "Forza la chiusura",
+ end_process_force_confirm:
+ "Sei sicuro di voler forzare la chiusura di questo processo?",
+ end_soft: "Chiudi",
+ enlarged_qr_code: "QR Code ingrandito",
+ enter_password_to_confirm_delete_user:
+ "Inserisci la tua password per confermare l'eliminazione del tuo account.",
+ error_message_is_missing: "Messaggio di errore mancante.",
+ error_unknown_cause: "Errore sconosciuto.",
+ error_uploading_files: "Errore durante l'upload dei file.",
+ favorites: "Preferiti",
+ feedback: "Feedback",
+ feedback_c2a:
+ "Usa il form qua sotto per inviarci feedback, commenti, e segnalarci dei bug.",
+ feedback_sent_confirmation:
+ "Grazie per averci contattato. Se hai un indirizzo email associato al tuo account, ti ricontatteremo il prima possibile.",
+ fit: "Adatta",
+ folder: "Cartella",
+ force_quit: "Forza la chiusura",
+ forgot_pass_c2a: "Password dimenticata?",
+ from: "Da",
+ general: "Generale",
+ get_a_copy_of_on_puter: `Ottieni una copia di '%%' su Puter.com!`,
+ get_copy_link: "Ottieni link di copia",
+ hide_all_windows: "Nascondi tutte le finestre",
+ home: "Home",
+ html_document: "Documento HTML",
+ hue: "Tonalità",
+ image: "Immagine",
+ incorrect_password: "Password errata",
+ invite_link: "Link d’invito",
+ item: "elemento",
+ items_in_trash_cannot_be_renamed: `Impossibile rinominare un elemento nel Cestino. Per rinominarlo, è necessario ripristinarlo.`,
+ jpeg_image: "Immagine JPEG",
+ keep_in_taskbar: "Blocca nella barra delle applicazioni",
+ language: "Lingua",
+ license: "Licenza",
+ lightness: "Luminosità",
+ link_copied: "Link copiato",
+ loading: "Caricamento",
+ log_in: "Accedi",
+ log_into_another_account_anyway: "Accedi comunque con un altro account",
+ log_out: "Disconnettiti",
+ looks_good: "Sembra buono!",
+ manage_sessions: "Gestisci le sessioni",
+ menubar_style: "Stile della barra dei menu",
+ menubar_style_desktop: "Scrivania",
+ menubar_style_system: "Sistema",
+ menubar_style_window: "Finestra",
+ modified: "Modificato",
+ move: "Sposta",
+ moving_file: "Spostamento in corso %%",
+ my_websites: "I miei siti web",
+ name: "Nome",
+ name_cannot_be_empty: "Il nome non può essere vuoto.",
+ name_cannot_contain_double_period: "Il nome non può contenere '..' .",
+ name_cannot_contain_period: "Il nome non può contenere '.' .",
+ name_cannot_contain_slash: "Il nome non può contenere '/' .",
+ name_must_be_string: "Il nome può contenere una sola linea.",
+ name_too_long: `Il nome non può essere più lungo di %% caratteri.`,
+ new: "Nuovo",
+ new_email: "Nuova Email",
+ new_folder: "Nuova Cartella",
+ new_password: "Nuova Password",
+ new_username: "Nuovo Nome Utente",
+ no: "No",
+ no_dir_associated_with_site:
+ "Nessuna directory è stata associata all’indirizzo.",
+ no_websites_published:
+ "Non hai pubblicato nessun sito web. Clicca tasto destro su una cartella per iniziare.",
+ ok: "OK",
+ open: "Apri",
+ open_in_new_tab: "Apri in una nuova scheda",
+ open_in_new_window: "Apri in una nuova finestra",
+ open_with: "Apri con",
+ original_name: "Nome originale",
+ original_path: "Percorso originale",
+ oss_code_and_content: "Contenuto e software Open Source",
+ password: "Password",
+ password_changed: "Password modificata.",
+ password_recovery_rate_limit:
+ "Hai raggiunto il limite di richieste; per favore attendi qualche minuto. Per evitarlo in futuro evita di ricaricare la pagina troppe volte.",
+ password_recovery_token_invalid:
+ "Questo token per il recupero della password non è valido.",
+ password_recovery_unknown_error:
+ "Errore sconosciuto. Per favore riprova più tardi.",
+ password_required: "Password richiesta.",
+ password_strength_error:
+ "La password deve essere lunga almeno 8 caratteri e contenete almeno una maiuscola, una minuscola, un numero e un carattere speciale.",
+ passwords_do_not_match:
+ "Le caselle `Nuova Password` and `Conferma Nuova Password` non corrispondono.",
+ paste: "Incolla",
+ paste_into_folder: "Incolla nella cartella",
+ path: "Percorso",
+ personalization: "Personalizzazione",
+ pick_name_for_website: "Scegli un nome per il tuo sito web:",
+ picture: "Immagine",
+ pictures: "Immagini",
+ plural_suffix: "i",
+ powered_by_puter_js: `Realizzato con {{link=docs}}Puter.js{{/link}}`,
+ preparing: "Preparazione in corso...",
+ preparing_for_upload: "Preparazione per l’upload...",
+ print: "Stampa",
+ privacy: "Privacy",
+ proceed_to_login: "Procedi con il login",
+ proceed_with_account_deletion: "Continua con l'eliminazione dell'account",
+ process_status_initializing: "Inizializzando",
+ process_status_running: "In esecuzione",
+ process_type_app: "App",
+ process_type_init: "Inizializzazione",
+ process_type_ui: "UI",
+ properties: "Proprietà",
+ public: "Pubblico",
+ publish: "Pubblica",
+ publish_as_website: "Pubblica come sito web",
+ puter_description: `Puter è un cloud personale che mette la privacy al primo posto, per conservare tutti i tuoi file, app e giochi in un unico luogo sicuro, accessibile da qualsiasi luogo e in qualsiasi momento.`,
+ reading_file: "Leggendo %strong%",
+ recent: "Recenti",
+ recommended: "Consigliati",
+ recover_password: "Ripristina la Password",
+ refer_friends_c2a:
+ "Ottieni 1 GB di spazio di archiviazione per ogni amico che crea un account e conferma l’email su Puter. Anche il tuo amico riceverà dello spazio extra!",
+ refer_friends_social_media_c2a: `Ottieni 1GB di spazio di spazio di archiviazione gratuito su Puter.com!`,
+ refresh: "Ricarica",
+ release_address_confirmation: `Sei sicuro di voler liberare questo indirizzo?`,
+ remove_from_taskbar: "Sblocca dalla barra delle applicazioni",
+ rename: "Rinomina",
+ repeat: "Ripeti",
+ replace: "Sostituisci",
+ replace_all: "Sostituisci tutto",
+ resend_confirmation_code: "Invia di nuovo il codice di conferma",
+ reset_colors: "Ripristina i colori",
+ restart_puter_confirm: "Sei sicuro di voler riavviare Puter?",
+ restore: "Ripristina",
+ save: "Salva",
+ saturation: "Saturazione",
+ save_account: "Salva Account",
+ save_account_to_get_copy_link:
+ "È necessario creare un account per procedere.",
+ save_account_to_publish: "È necessario creare un account per procedere.",
+ save_session: "Salva sessione",
+ save_session_c2a:
+ "Crea un account per salvare la tua sessione e non perdere i tuoi dati.",
+ scan_qr_c2a:
+ "Scansiona il codice qua sotto per utilizzare questa sessione da altri dispositivi",
+ scan_qr_2fa: "Scansiona il codice QR con la tua app di autenticazione",
+ scan_qr_generic: "Scansiona il codice QR usando il tuo smartphone",
+ search: "Search",
+ seconds: "seconds",
+ security: "Sicurezza",
+ select: "Seleziona",
+ selected: "Selezionato",
+ select_color: "Seleziona un colore…",
+ sessions: "Sessioni",
+ send: "Invia",
+ send_password_recovery_email:
+ "Invia email per il ripristino della password",
+ session_saved:
+ "Grazie per aver creato un account. La sessione è stata salvata",
+ settings: "Impostazioni",
+ set_new_password: "Imposta una nuova Password",
+ share: "Condividi",
+ share_to: "Condividi con",
+ share_with: "Condividi con",
+ shortcut_to: "Scorciatoia per",
+ show_all_windows: "Mostra tutte le finestre",
+ show_hidden: "Mostra nascosti",
+ sign_in_with_puter: "Accedi con Puter",
+ sign_up: "Registrati",
+ signing_in: "Accesso in corso…",
+ size: "Dimensione",
+ skip: "Salta",
+ something_went_wrong: "Qualcosa è andato storto.",
+ sort_by: "Ordina per",
+ start: "Start",
+ status: "Stato",
+ storage_usage: "Utilizzo dello spazio",
+ storage_puter_used: "utilizzato da Puter",
+ taking_longer_than_usual:
+ "Il processo in corso ci sta mettendo più del solito. Attendere prego...",
+ task_manager: "Gestione attività",
+ taskmgr_header_name: "Nome",
+ taskmgr_header_status: "Stato",
+ taskmgr_header_type: "Tipo",
+ terms: "Termini",
+ text_document: "Documento di testo",
+ tos_fineprint: `Cliccando su 'Crea un account gratis' accetti i {{link=terms}}Termini di Servizio{{/link}} e l'{{link=privacy}}Informativa sulla Privacy{{/link}} di Puter.`,
+ transparency: "Trasparenza",
+ trash: "Cestino",
+ two_factor: "Autenticazione a due fattori",
+ two_factor_disabled: "2FA Disabilitata",
+ two_factor_enabled: "2FA Abilitata",
+ type: "Tipo",
+ type_confirm_to_delete_account:
+ "Scrivi 'conferma' per eliminare il tuo account.",
+ ui_colors: "Colori dell'interfaccia",
+ ui_manage_sessions: "Session Manager",
+ ui_revoke: "Revoca",
+ undo: "Annulla",
+ unlimited: "Illimitato",
+ unzip: "Estrai",
+ upload: "Carica",
+ upload_here: "Carica qui",
+ usage: "Utilizzo",
+ username: "Nome Utente",
+ username_changed: "Nome utente aggiornato con successo.",
+ username_required: "Il nome utente è richiesto.",
+ versions: "Versioni",
+ videos: "Video",
+ visibility: "Visibilità",
+ yes: "Sì",
+ yes_release_it: "Si, rilascialo",
+ you_have_been_referred_to_puter_by_a_friend:
+ "Sei stato invitato su Puter da un amico!",
+ zip: "File compresso",
+ zipping_file: "Compressione di %strong%",
- // === 2FA Setup ===
- setup2fa_1_step_heading: 'Apri la tua app di autenticazione',
- setup2fa_1_instructions: `
+ // === 2FA Setup ===
+ setup2fa_1_step_heading: "Apri la tua app di autenticazione",
+ setup2fa_1_instructions: `
Puoi utilizzare qualsiasi app di autenticazione che supporti il protocollo TOTP (Time-based One-Time Password).
Ci sono molte opzioni tra cui scegliere, ma se non sei sicuro,
Authy
è una scelta valida sia per Android che per iOS.
`,
- setup2fa_2_step_heading: 'Scansiona il codice QR',
- setup2fa_3_step_heading: 'Inserisci il codice a 6 cifre',
- setup2fa_4_step_heading: 'Copia i tuoi codici di recupero',
- setup2fa_4_instructions: `
+ setup2fa_2_step_heading: "Scansiona il codice QR",
+ setup2fa_3_step_heading: "Inserisci il codice a 6 cifre",
+ setup2fa_4_step_heading: "Copia i tuoi codici di recupero",
+ setup2fa_4_instructions: `
Questi codici di recupero sono l'unico modo per accedere al tuo account se perdi il telefono o non puoi utilizzare la tua app di autenticazione.
Assicurati di conservarli in un luogo sicuro.
`,
- setup2fa_5_step_heading: 'Conferma la configurazione del 2FA',
- setup2fa_5_confirmation_1: 'Ho salvato i miei codici di recupero in un luogo sicuro',
- setup2fa_5_confirmation_2: 'Sono pronto per abilitare la 2FA',
- setup2fa_5_button: 'Abilita la 2FA',
+ setup2fa_5_step_heading: "Conferma la configurazione del 2FA",
+ setup2fa_5_confirmation_1:
+ "Ho salvato i miei codici di recupero in un luogo sicuro",
+ setup2fa_5_confirmation_2: "Sono pronto per abilitare la 2FA",
+ setup2fa_5_button: "Abilita la 2FA",
- // === 2FA Login ===
- login2fa_otp_title: 'Inserisci il codice 2FA',
- login2fa_otp_instructions: 'Inserisci il codice a 6 cifre dalla tua app di autenticazione.',
- login2fa_recovery_title: 'Inserisci un codice di recupero',
- login2fa_recovery_instructions: 'Inserisci uno dei tuoi codici di recupero per accedere al tuo account.',
- login2fa_use_recovery_code: 'Usa un codice di recupero',
- login2fa_recovery_back: 'Indietro',
- login2fa_recovery_placeholder: 'XXXXXXXX',
+ // === 2FA Login ===
+ login2fa_otp_title: "Inserisci il codice 2FA",
+ login2fa_otp_instructions:
+ "Inserisci il codice a 6 cifre dalla tua app di autenticazione.",
+ login2fa_recovery_title: "Inserisci un codice di recupero",
+ login2fa_recovery_instructions:
+ "Inserisci uno dei tuoi codici di recupero per accedere al tuo account.",
+ login2fa_use_recovery_code: "Usa un codice di recupero",
+ login2fa_recovery_back: "Indietro",
+ login2fa_recovery_placeholder: "XXXXXXXX",
- "change": 'Cambia', // In English: "Change"
- "clock_visibility": 'Visibilità orologio', // In English: "Clock Visibility"
- "reading": 'Legendo %strong%', // In English: "Reading %strong%"
- "writing": 'Scrivendo %strong%', // In English: "Writing %strong%"
- "unzipping": 'Decompressione di %strong%', // In English: "Unzipping %strong%"
- "sequencing": 'Sequenziamento di %strong%', // In English: "Sequencing %strong%"
- "zipping": 'Compressione di %strong%', // In English: "Zipping %strong%"
- "Editor": 'Editore', // In English: "Editor"
- "Viewer": 'Visualizatore', // In English: "Viewer"
- "People with access": 'Persone con accesso', // In English: "People with access"
- "Share With…": 'Condividi con…', // In English: "Share With…"
- "Owner": 'Proprietario', // In English: "Owner"
- "You can't share with yourself.": "Non puoi condividere con te stesso", // In English: "You can't share with yourself."
- "This user already has access to this item": "Questo utente ha già accesso a questo file", // In English: "This user already has access to this item"
+ change: "Cambia", // In English: "Change"
+ clock_visibility: "Visibilità orologio", // In English: "Clock Visibility"
+ reading: "Legendo %strong%", // In English: "Reading %strong%"
+ writing: "Scrivendo %strong%", // In English: "Writing %strong%"
+ unzipping: "Decompressione di %strong%", // In English: "Unzipping %strong%"
+ sequencing: "Sequenziamento di %strong%", // In English: "Sequencing %strong%"
+ zipping: "Compressione di %strong%", // In English: "Zipping %strong%"
+ Editor: "Editore", // In English: "Editor"
+ Viewer: "Visualizatore", // In English: "Viewer"
+ "People with access": "Persone con accesso", // In English: "People with access"
+ "Share With…": "Condividi con…", // In English: "Share With…"
+ Owner: "Proprietario", // In English: "Owner"
+ "You can't share with yourself.": "Non puoi condividere con te stesso", // In English: "You can't share with yourself."
+ "This user already has access to this item":
+ "Questo utente ha già accesso a questo file", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "Modifica", // In English: "Change"
+ "billing.cancel": "Annulla", // In English: "Cancel"
+ "billing.download_invoice": "Scarica", // In English: "Download"
+ "billing.payment_method": "Metodo di pagamento", // In English: "Payment Method"
+ "billing.payment_method_updated": "Metodo di pagamento aggiornato!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Conferma metodo di pagamento", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Storico dei pagamenti", // In English: "Payment History"
+ "billing.refunded": "Rimborsato", // In English: "Refunded"
+ "billing.paid": "Pagato", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Riprendi abbonamento", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Il tuo abbonamento è stato annullato.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Avrai ancora accesso al tuo abbonamento fino alla fine di questo periodo di fatturazione.",
+ // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Gratuito", // In English: "Free"
+ "billing.offering.pro": "Professionale", // In English: "Professional"
+ "billing.offering.business": "Business", // In English: "Business"
+ "billing.cloud_storage": "Archiviazione cloud", // In English: "Cloud Storage"
+ "billing.ai_access": "Accesso AI", // In English: "AI Access"
+ "billing.bandwidth": "Larghezza di banda", // In English: "Bandwidth"
+ "billing.apps_and_games": "App e Giochi", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Passa al piano %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Passa a %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Impostazioni di pagamento", // In English: "Payment Setup"
+ "billing.back": "Indietro", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Ora sei abbonato al piano %strong%.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Ora sei abbonato.", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Sei sicuro di voler annullare il tuo abbonamento?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Impostazione abbonamento", // In English: "Subscription Setup"
+ "billing.cancel_it": "Annulla", // In English: "Cancel It"
+ "billing.keep_it": "Mantieni", // In English: "Keep It"
+ "billing.subscription_resumed": "Il tuo abbonamento %strong% è stato ripristinato!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Aggiorna ora", // In English: "Upgrade Now"
+ "billing.upgrade": "Aggiorna", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Attualmente sei nel piano gratuito.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Scarica ricevuta", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Si è verificato un problema durante il controllo dello stato dell'abbonamento.",
+ // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "La tua email non è stata confermata. Ti invieremo un codice per completare la conferma.",
+ // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Hai annullato il tuo abbonamento. Passerà automaticamente al piano gratuito alla fine del periodo di fatturazione. Non ti verrà addebitato nuovamente a meno che non ti iscrivi di nuovo.",
+ // 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": "Il tuo piano attuale è valido fino alla fine del periodo di fatturazione.",
+ // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Piano attuale", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Abbonamento annullato (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Gestisci", // In English: "Manage"
+ "billing.limited": "Limitato", // In English: "Limited"
+ "billing.expanded": "Espanso", // In English: "Expanded"
+ "billing.accelerated": "Accelerato", // In English: "Accelerated"
+ "billing.enjoy_msg": "Goditi %% di spazio di archiviazione cloud e altri vantaggi.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
-export default it;
\ No newline at end of file
+export default it;
diff --git a/src/gui/src/i18n/translations/ja.js b/src/gui/src/i18n/translations/ja.js
index 98e3fe423e..c199b892b3 100644
--- a/src/gui/src/i18n/translations/ja.js
+++ b/src/gui/src/i18n/translations/ja.js
@@ -363,7 +363,54 @@ const ja = {
"Owner": "所有者", // In English: "Owner"
"You can't share with yourself.": "自分自身と共有することはできません。", // In English: "You can't share with yourself."
"This user already has access to this item": "このユーザーは既にこのアイテムにアクセスできます。", // In English: "This user already has access to this item"
-
+
+ "plural_suffix": "複数形接尾辞", // In English: "s"
+ "billing.change_payment_method": "支払い方法を変更", // In English: "Change"
+ "billing.cancel": "支払いをキャンセル", // In English: "Cancel"
+ "billing.download_invoice": "請求書をダウンロード", // In English: "Download"
+ "billing.payment_method": "支払い方法", // In English: "Payment Method"
+ "billing.payment_method_updated": "支払い方法が更新されました!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "支払い方法を確認", // In English: "Confirm Payment Method"
+ "billing.payment_history": "支払い履歴", // In English: "Payment History"
+ "billing.refunded": "返金されました", // In English: "Refunded"
+ "billing.paid": "支払い済み", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "サブスクリプションを再開", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "サブスクリプションがキャンセルされました。", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "この請求期間の終了までサブスクリプションを利用できます。", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "無料", // In English: "Free"
+ "billing.offering.pro": "プロフェッショナル", // In English: "Professional"
+ "billing.offering.business": "ビジネス", // In English: "Business"
+ "billing.cloud_storage": "クラウドストレージ", // In English: "Cloud Storage"
+ "billing.ai_access": "AIアクセス", // In English: "AI Access"
+ "billing.bandwidth": "データの転送量や速度", // In English: "Bandwidth"
+ "billing.apps_and_games": "アプリ&ゲーム", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "%strong%にアップグレード", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "%strong%に切り替え", // In English: "Switch to %strong%"
+ "billing.payment_setup": "支払い設定", // In English: "Payment Setup"
+ "billing.back": "戻る", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "現在%strong%プランに加入しています。", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "現在サブスクリプションに加入しています。", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "サブスクリプションをキャンセルしてもよろしいですか?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "サブスクリプションの設定", // In English: "Subscription Setup"
+ "billing.cancel_it": "キャンセルする", // In English: "Cancel It"
+ "billing.keep_it": "維持する", // In English: "Keep It"
+ "billing.subscription_resumed": "%strong%のサブスクリプションが再開されました!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "今すぐアップグレード", // In English: "Upgrade Now"
+ "billing.upgrade": "アップグレード", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "現在、無料プランに加入しています。", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "領収書をダウンロード", // In English: "Download Receipt"
+ "billing.subscription_check_error": "サブスクリプションの状況を確認中に問題が発生しました。", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "メールが確認されていません。確認コードをお送りします。", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "サブスクリプションをキャンセルしました。この請求期間の終了時に自動的に無料プランに切り替わります。再加入しない限り、追加料金は発生しません。", // 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": "この請求期間の終了時までの現在のプランを続ける。", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "現在のプラン", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "キャンセルされたサブスクリプション(%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "管理", // In English: "Manage"
+ "billing.limited": "制限付き", // In English: "Limited"
+ "billing.expanded": "拡張", // In English: "Expanded"
+ "billing.accelerated": "高速化", // In English: "Accelerated"
+ "billing.enjoy_msg": "クラウドストレージの%%とその他の特典をお楽しみください。", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/ko.js b/src/gui/src/i18n/translations/ko.js
index 7e03dc7b37..800005d7c4 100644
--- a/src/gui/src/i18n/translations/ko.js
+++ b/src/gui/src/i18n/translations/ko.js
@@ -17,6 +17,10 @@
* along with this program. If not, see .
*/
+// Translator note: It depends on the overall tone you are going for, however I would suggest changing verbs ending in 십시오 to 세요 for a more modern, user-friendly tone that aligns with conversational UI trends.
+// Translator note: 십시오 is very formal and better suited for official or enterprise contexts (e.g government websites), while 세요 feels approachable and appropriate for general users.
+// Translator note: I have noted down improvement suggestions below based on my knowledge of modern Korean user interfaces without changing the original translations for your reference.
+
const ko = {
name: "한국어",
english_name: "Korean",
@@ -24,7 +28,7 @@ const ko = {
dictionary: {
about: "정보",
account: "계정",
- account_password: "",
+ account_password: "계정 비밀번호", // Added translation: "account password"
access_granted_to: "접근 권한 부여",
add_existing_account: "기존 계정 추가",
all_fields_required: "모든 항목은 필수 입력사항입니다.",
@@ -53,29 +57,29 @@ const ko = {
confirm_2fa_setup: "코드를 인증 앱에 추가했습니다",
confirm_2fa_recovery: "복구 코드를 안전한 위치에 저장했습니다",
confirm_account_for_free_referral_storage_c2a:
- "계정을 생성하고 이메일 주소를 확인하여 1GB의 무료 저장 공간을 받으십시오. 친구도 1GB의 무료 저장 공간을 받게 됩니다.",
+ "계정을 생성하고 이메일 주소를 확인하여 1GB의 무료 저장 공간을 받으십시오. 친구도 1GB의 무료 저장 공간을 받게 됩니다.", // Improvement suggestion: "계정을 만들고 이메일 주소를 확인하면 1GB의 무료 저장 공간을 드립니다! 친구와 공유하면 친구도 1GB를 받을 수 있습니다."
confirm_code_generic_incorrect: "잘못된 코드입니다.",
confirm_code_generic_too_many_requests:
- "요청이 너무 많습니다. 몇 분만 기다려주십시오.",
+ "요청이 너무 많습니다. 몇 분만 기다려주십시오.", // Improvement suggestion: "요청이 너무 많습니다. 잠시만 기다려주세요."
confirm_code_generic_submit: "코드 제출",
confirm_code_generic_try_again: "재시도",
- confirm_code_generic_title: "확인 코드를 입력하십시오",
- confirm_code_2fa_instruction: "인증 앱의 6자리 코드를 입력하십시오.",
+ confirm_code_generic_title: "확인 코드를 입력하십시오", // Improvement suggestion: "인증 코드를 입력해주세요." (use 인증 if it's a verification/confirmation code)
+ confirm_code_2fa_instruction: "인증 앱의 6자리 코드를 입력하십시오.", // Improvement suggestion: "인증 앱의 6자리 코드를 입력해주세요."
confirm_code_2fa_submit_btn: "제출",
- confirm_code_2fa_title: "2FA 코드를 입력하십시오",
+ confirm_code_2fa_title: "2FA 코드를 입력하십시오", // Improvement suggestion: "2FA 코드를 입력해주세요."
confirm_delete_multiple_items:
- "정말로 이 항목들을 영구적으로 삭제하시겠습니까?",
+ "정말로 이 항목들을 영구적으로 삭제하시겠습니까?", // Improvement suggestion: "항목들을 정말 영구적으로 삭제하시겠습니까?" (if it's a selection you could add "selected" to items like this: "선택된 항목들을 정말 영구적으로 삭제하시겠습니까?")
confirm_delete_single_item: "이 항목을 영구적으로 삭제하시겠습니까?",
confirm_open_apps_log_out:
"열려있는 앱들이 있습니다. 정말로 로그아웃 하시겠습니까?",
confirm_new_password: "새 비밀번호 확인",
confirm_delete_user:
- "정말로 계정을 삭제하시겠습니까? 모든 파일과 데이터가 영구적으로 삭제됩니다. 이 작업은 취소될 수 없습니다.",
+ "정말로 계정을 삭제하시겠습니까? 모든 파일과 데이터가 영구적으로 삭제됩니다. 이 작업은 취소될 수 없습니다.", // Improvement suggestion: "정말 계정을 삭제하시겠습니까? 모든 파일과 데이터가 영구적으로 삭제되며, 이 작업은 취소할 수 없습니다."
confirm_delete_user_title: "계정 삭제?",
confirm_session_revoke: "정말로 이 세션을 취소하시겠습니까?",
- confirm_your_email_address: "이메일 주소를 확인하십시오",
+ confirm_your_email_address: "이메일 주소를 확인하십시오", // Improvement suggestion: "이메일 주소를 확인해주세요."
contact_us: "문의하기",
- contact_us_verification_required: "인증된 이메일 주소가 있어야 합니다.",
+ contact_us_verification_required: "인증된 이메일 주소가 있어야 합니다.", // Improvement suggestion: "이메일 인증이 필요합니다."
contain: "포함",
continue: "계속",
copy: "복사",
@@ -109,7 +113,7 @@ const ko = {
dir_published_as_website: `%strong% 다음에 게시되었습니다:`,
disable_2fa: "2FA 비활성화",
disable_2fa_confirm: "정말로 2FA를 비활성화 하시겠습니까?",
- disable_2fa_instructions: "2FA 비활성화를 하려면 비밀번호를 입력하십시오.",
+ disable_2fa_instructions: "2FA 비활성화를 하려면 비밀번호를 입력하십시오.", // Improvement suggestion: "2FA 비활성화를 하려면 비밀번호를 입력해주세요."
disassociate_dir: "디렉토리 연결 해제",
documents: "문서",
dont_allow: "허용하지 않음",
@@ -118,7 +122,7 @@ const ko = {
downloading: "다운로드 중",
email: "이메일",
email_change_confirmation_sent:
- "새 이메일 주소로 확인 메일이 전송되었습니다. 받은 편지함을 확인하시고 안내에 따라 절차를 완료하십시오.",
+ "새 이메일 주소로 확인 메일이 전송되었습니다. 받은 편지함을 확인하시고 안내에 따라 절차를 완료하십시오.", // Improvement suggestion: "새 이메일 주소로 확인 메일이 전송되었습니다. 받은 편지함을 확인 후 안내에 따라 절차를 완료해주세요."
email_invalid: "이메일이 유효하지 않습니다.",
email_or_username: "이메일 또는 사용자 이름",
email_required: "이메일은 필수 입력사항입니다.",
@@ -131,16 +135,16 @@ const ko = {
end_soft: "소프트 종료",
enlarged_qr_code: "확대된 QR 코드",
enter_password_to_confirm_delete_user:
- "계정 삭제를 승인하려면 비밀번호를 입력하십시오.",
+ "계정 삭제를 승인하려면 비밀번호를 입력하십시오.", // Improvement suggestion: "계정 삭제를 승인하려면 비밀번호를 입력해주세요."
error_message_is_missing: "오류 메세지를 찾을 수 없습니다.",
error_unknown_cause: "알 수 없는 오류가 발생했습니다.",
- error_uploading_files: "파일들을 업로드 하는데 실패했습니다",
+ error_uploading_files: "파일들을 업로드 하는데 실패했습니다", // Improvement suggestion: "파일 업로드가 실패했습니다"
favorites: "즐겨찾기",
feedback: "피드백",
feedback_c2a:
- "아래 양식을 사용하여 피드백, 의견 및 버그 보고를 보내십시오.",
+ "아래 양식을 사용하여 피드백, 의견 및 버그 보고를 보내십시오.", // Improvement suggestion: "아래 양식을 통해 피드백, 의견 또는 버그 보고를 보내주세요."
feedback_sent_confirmation:
- "문의해 주셔서 감사합니다. 계정에 이메일이 연결되어 있으면 가능한 빨리 회신 드리겠습니다.",
+ "문의해 주셔서 감사합니다. 계정에 이메일이 연결되어 있으면 가능한 빨리 회신 드리겠습니다.", // Improvement suggestion: "문의해 주셔서 감사합니다. 계정에 이메일이 연결되어 있다면 최대한 빨리 답변드리겠습니다."
fit: "맞춤",
folder: "폴더",
force_quit: "강제 종료",
@@ -157,7 +161,7 @@ const ko = {
incorrect_password: "잘못된 비밀번호",
invite_link: "초대 링크",
item: "개 항목",
- items_in_trash_cannot_be_renamed: `이 항목은 휴지통에 있기 때문에 이름을 바꿀 수 없습니다. 이 항목의 이름을 바꾸려면 먼저 휴지통에서 끌어내십시오.`,
+ items_in_trash_cannot_be_renamed: `이 항목은 휴지통에 있기 때문에 이름을 바꿀 수 없습니다. 이 항목의 이름을 바꾸려면 먼저 휴지통에서 끌어내십시오.`, // Improvement suggestion: "이 항목은 휴지통에 있어 이름을 변경할 수 없습니다. 이름을 변경하려면 먼저 휴지통에서 복원해주세요."
jpeg_image: "JPEG 이미지",
keep_in_taskbar: "작업 표시줄에 유지",
language: "언어",
@@ -204,11 +208,11 @@ const ko = {
password: "비밀번호",
password_changed: "비밀번호가 변경되었습니다.",
password_recovery_rate_limit:
- "속도 제한에 도달했습니다. 몇 분만 기다려 주십시오. 앞으로 이 문제를 방지하려면 페이지를 너무 많이 다시 로드하지 마십시오.",
+ "속도 제한에 도달했습니다. 몇 분만 기다려 주십시오. 앞으로 이 문제를 방지하려면 페이지를 너무 많이 다시 로드하지 마십시오.", // Improvement suggestion: "속도 제한에 도달했습니다. 잠시만 기다려주세요. 앞으로 이런 문제가 발생하지 않도록 페이지를 자주 새로고침하지 마세요."
password_recovery_token_invalid:
- "이 비밀번호 복구 토큰은 더 이상 유효하지 않습니다.",
+ "이 비밀번호 복구 토큰은 더 이상 유효하지 않습니다.", // Improvement suggestion: "유효하지 않은 비밀번호 복구 토큰입니다."
password_recovery_unknown_error:
- "알 수 없는 오류가 발생했습니다. 나중에 다시 시도해주십시오.",
+ "알 수 없는 오류가 발생했습니다. 나중에 다시 시도해주십시오.", // Improvement suggestion: "알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해주세요."
password_required: "비밀번호는 필수 입력사항 입니다.",
password_strength_error:
"비밀번호는 반드시 최소 8자 이상이어야 하며 최소 대문자 1개, 소문자 1개, 숫자 1개, 특수문자 1개를 포함해야 합니다.",
@@ -244,8 +248,8 @@ const ko = {
recommended: "추천",
recover_password: "비밀번호 찾기",
refer_friends_c2a:
- "Puter에서 계정을 생성하고 확인한 친구마다 1GB를 받으십시오. 친구도 1GB를 받게 됩니다!",
- refer_friends_social_media_c2a: `Puter.com에서 1GB의 무료 저장 공간을 받으십시오!`,
+ "Puter에서 계정을 생성하고 확인한 친구마다 1GB를 받으십시오. 친구도 1GB를 받게 됩니다!", // Improvement suggestion: "Puter에서 계정을 만들고 확인한 친구마다 1GB를 받아보세요. 친구도 1GB를 받게 됩니다!"
+ refer_friends_social_media_c2a: `Puter.com에서 1GB의 무료 저장 공간을 받으십시오!`, // Improvement suggestion: "Puter.com에서 1GB의 무료 저장 공간을 받아보세요!"
refresh: "새로 고침",
release_address_confirmation: `이 주소를 해제하시겠습니까?`,
remove_from_taskbar: "작업 표시줄에서 제거",
@@ -253,20 +257,20 @@ const ko = {
repeat: "반복",
replace: "교체",
replace_all: "모두 교체",
- resend_confirmation_code: "확인 코드 다시 보내기",
+ resend_confirmation_code: "확인 코드 다시 보내기", // Improvement suggestion: "인증 코드 재전송"
reset_colors: "색상 초기화",
restart_puter_confirm: "정말 Puter를 다시 시작하시겠습니까?",
restore: "복원",
save: "저장",
saturation: "채도",
save_account: "계정 저장",
- save_account_to_get_copy_link: "계속하려면 계정을 생성하십시오.",
- save_account_to_publish: "계속하려면 계정을 생성하십시오.",
+ save_account_to_get_copy_link: "계속하려면 계정을 생성하십시오.", // Improvement suggestion: "계속하려면 계정을 만들어주세요."
+ save_account_to_publish: "계속하려면 계정을 생성하십시오.", // Improvement suggestion: "계속하려면 계정을 만들어주세요."
save_session: "세션 저장",
save_session_c2a:
- "현재 세션을 저장하고 작업을 잃지 않으려면 계정을 생성하십시오.",
+ "현재 세션을 저장하고 작업을 잃지 않으려면 계정을 생성하십시오.", // Improvement suggestion: "현재 세션을 저장하고 작업을 잃지 않으려면 계정을 만들어주세요."
scan_qr_c2a:
- "다른 기기에서 이 세션으로 로그인하려면 아래 코드를 스캔하십시오",
+ "다른 기기에서 이 세션으로 로그인하려면 아래 코드를 스캔하십시오", // Improvement suggestion: change 스캔하십시오 to 스캔해주세요 for the next 3 lines.
scan_qr_2fa: "인증 앱으로 QR 코드를 스캔하십시오.",
scan_qr_generic: "휴대전화나 다른 기기로 QR 코드를 스캔하십시오",
search: "검색",
@@ -278,7 +282,7 @@ const ko = {
sessions: "세션",
send: "보내기",
send_password_recovery_email: "비밀번호 복구 이메일 보내기",
- session_saved: "계정을 생성해 주셔서 감사합니다. 이 세션이 저장되었습니다.",
+ session_saved: "계정을 생성해 주셔서 감사합니다. 이 세션이 저장되었습니다.", // Improvement suggestion: "계정을 만들어주셔서 감사합니다. 현재 세션이 저장되었습니다."
settings: "설정",
set_new_password: "새 비밀번호 설정",
share: "공유",
@@ -292,14 +296,15 @@ const ko = {
signing_in: "로그인 중…",
size: "크기",
skip: "건너뛰기",
- something_went_wrong: "무언가 잘못되었습니다.",
+ something_went_wrong: "무언가 잘못되었습니다.", // Improvement suggestion: "문제가 발생했습니다" ("There was a problem") which is more widely used for errors.
sort_by: "정렬 기준",
start: "시작",
status: "상태",
storage_usage: "저장 공간 사용량",
storage_puter_used: "Puter에서 사용 중",
taking_longer_than_usual:
- "보통보다 조금 더 오래 걸립니다. 잠시만 기다려 주십시오...",
+ "보통보다 조금 더 오래 걸립니다. 잠시만 기다려 주십시오...", // Improvement suggestion: "평소보다 조금 더 오래 걸리고 있습니다. 잠시만 기다려주세요..."
+
task_manager: "작업 관리자",
taskmgr_header_name: "이름",
taskmgr_header_status: "상태",
@@ -314,7 +319,7 @@ const ko = {
two_factor_enabled: "2FA 활성화됨",
type: "유형",
type_confirm_to_delete_account:
- "계정을 삭제하려면 'confirm'을 입력하십시오.",
+ "계정을 삭제하려면 'confirm'을 입력하십시오.", // Improvement suggestion: "계정을 삭제하려면 'confirm'을 입력해주세요."
ui_colors: "UI 색상",
ui_manage_sessions: "세션 관리자",
ui_revoke: "취소",
@@ -325,62 +330,118 @@ const ko = {
upload_here: "여기에 업로드",
usage: "사용량",
username: "사용자 이름",
- username_changed: "사용자 이름이 성공적으로 업데이트되었습니다.",
+ username_changed: "사용자 이름이 성공적으로 업데이트되었습니다.", // Improvement suggestion: "사용자 이름이 변경되었습니다." (simplified)
username_required: "사용자 이름은 필수 입력사항입니다.",
versions: "버전",
videos: "동영상",
- visibility: "가시성",
+ visibility: "가시성", // This depends on the specific context - if it means that content is visible/public or hidden/private, I would suggest changing it to "공개 여부" if the user can choose Yes/No or simply 공개 for visible/public) and 비공개 for invisible/private.
yes: "예",
yes_release_it: "예, 해제합니다",
- you_have_been_referred_to_puter_by_a_friend: "친구가 Puter로 추천했습니다!",
+ you_have_been_referred_to_puter_by_a_friend: "친구가 Puter로 추천했습니다!", // Improvement suggestion: "친구가 Puter를 추천했습니다!"
zip: "압축",
zipping_file: "%strong% 압축 중",
// === 2FA Setup ===
- setup2fa_1_step_heading: "인증 앱을 여십시오",
+ setup2fa_1_step_heading: "인증 앱을 여십시오", // Improvement suggestion: "인증 앱을 열어주세요."
setup2fa_1_instructions: `
시간 기반 일회용 비밀번호(TOTP) 프로토콜을 지원하는 모든 인증 앱을 사용할 수 있습니다.
선택할 수 있는 앱은 많지만, 잘 모르겠다면 안드로이드 및 iOS용
Authy
가 무난한 선택입니다.
`,
- setup2fa_2_step_heading: "QR 코드를 스캔하십시오",
- setup2fa_3_step_heading: "6자리 코드를 입력하십시오",
- setup2fa_4_step_heading: "복구 코드를 복사하십시오",
+ setup2fa_2_step_heading: "QR 코드를 스캔하십시오", // Improvement suggestion: "QR 코드를 스캔해주세요"
+ setup2fa_3_step_heading: "6자리 코드를 입력하십시오", // Improvement suggestion: "6자리 코드를 입력해주세요"
+ setup2fa_4_step_heading: "복구 코드를 복사하십시오", // Improvement suggestion: "복구 코드를 복사해주세요"
setup2fa_4_instructions: `
이 복구코드들은 휴대전화를 잃어버리거나 인증 앱을 사용할 수 없을 때 계정에 접속할 수 있는 유일한 수단입니다.
반드시 안전한 장소에 보관하세요.
- `,
+ `, // Improvement suggestion: "복구 코드는 휴대전화를 분실하거나 인증 앱을 사용할 수 없을 때 계정에 접속할 수 있는 유일한 방법입니다. 반드시 안전한 장소에 보관하세요."
setup2fa_5_step_heading: "2FA 설정 확인",
setup2fa_5_confirmation_1: "복구 코드를 안전한 위치에 저장했습니다",
setup2fa_5_confirmation_2: "2FA를 활성화할 준비가 되었습니다",
setup2fa_5_button: "2FA 활성화",
// === 2FA Login ===
- login2fa_otp_title: "2FA 코드를 입력하십시오",
- login2fa_otp_instructions: "인증 앱의 6자리 코드를 입력하십시오.",
- login2fa_recovery_title: "복구코드를 입력하십시오",
+ login2fa_otp_title: "2FA 코드를 입력하십시오", // Improvement suggestion: "2FA 코드를 입력해주세요"
+ login2fa_otp_instructions: "인증 앱의 6자리 코드를 입력하십시오.", // Improvement suggestion: "인증 앱의 6자리 코드를 입력해주세요."
+ login2fa_recovery_title: "복구코드를 입력하십시오", // Improvement suggestion: "복구코드를 입력해주세요"
login2fa_recovery_instructions:
- "계정 접속을 위해 복구코드들 중 하나를 입력하십시오.",
+ "계정 접속을 위해 복구코드들 중 하나를 입력하십시오.", // Improvement suggestion: "계정에 접속하려면 복구코드 중 하나를 입력해주세요."
login2fa_use_recovery_code: "복구코드 사용",
- login2fa_recovery_back: "뒤로 가기",
+ login2fa_recovery_back: "뒤로 가기", // Improvement suggestion: "뒤로"
login2fa_recovery_placeholder: "XXXXXXXX",
- "account_password": "계정 비밀번호 인증",
- "change": "변경",
- "clock_visibility": "시계 표시 설정",
- "reading": `%strong% 읽는 중`,
- "writing": `%strong% 기록 중`,
- "unzipping": `%strong% 압축 해제 중`,
- "sequencing": `%strong% 순서 처리 중`,
- "zipping": `%strong% 압축 중`,
- "Editor": "편집자", // If it refers to a person, the correct translation is "편집자" ,If it refers to the tool or software, the translation would be "편집기"
- "Viewer": "조회자", // If it refers to a person, the correct translation is "조회자" ,If it refers to the tool or software, the translation would be "뷰어"
- "People with access": "권한 보유자",
+ account_password: "계정 비밀번호 인증",
+ change: "변경",
+ clock_visibility: "시계 표시 설정",
+ reading: `%strong% 읽는 중`,
+ writing: `%strong% 기록 중`,
+ unzipping: `%strong% 압축 해제 중`,
+ sequencing: `%strong% 순서 처리 중`,
+ zipping: `%strong% 압축 중`,
+ Editor: "편집자", // If it refers to a person, the correct translation is "편집자" ,If it refers to the tool or software, the translation would be "편집기"
+ Viewer: "조회자", // If it refers to a person, the correct translation is "조회자" ,If it refers to the tool or software, the translation would be "뷰어"
+ "People with access": "권한 보유자",
"Share With…": "공유 대상...",
- "Owner": "소유자",
- "You can't share with yourself.": "자기 자신과는 공유할 수 없습니다.",
- "This user already has access to this item": "이 사용자는 이미 접근 권한이 있습니다.",
+ Owner: "소유자",
+ "You can't share with yourself.": "자기 자신과는 공유할 수 없습니다.",
+ "This user already has access to this item":
+ "이 사용자는 이미 접근 권한이 있습니다.",
+
+ "billing.change_payment_method": "결제 수단 변경", // added "payment method"
+ "billing.cancel": "취소",
+ "billing.download_invoice": "청구서 다운로드", // added "invoice"
+ "billing.payment_method": "결제 수단", // changed 방법 to 수단 which is more widely used in payment UIs
+ "billing.payment_method_updated": "결제 수단이 변경되었습니다!", // changed to more natural Korean
+ "billing.confirm_payment_method": "결제 수단 확인", // In English: "Confirm Payment Method"
+ "billing.payment_history": "결제 내역", // In English: "Payment History"
+ "billing.refunded": "환불 완료", // In English: "Refunded"
+ "billing.paid": "결제 완료", // In English: "Paid"
+ "billing.ok": "확인", // In English: "OK"
+ "billing.resume_subscription": "구독 재개", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "구독이 취소되었습니다.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description":
+ "청구 기간이 끝날 때까지 구독을 계속 이용할 수 있습니다.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "무료", // In English: "Free"
+ "billing.offering.pro": "프로", // In English: "Professional"
+ "billing.offering.business": "비즈니스", // In English: "Business"
+ "billing.cloud_storage": "클라우드 저장소", // In English: "Cloud Storage"
+ "billing.ai_access": "AI 접근", // In English: "AI Access"
+ "billing.bandwidth": "대역폭", // In English: "Bandwidth"
+ "billing.apps_and_games": "앱 및 게임", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "%strong%으로 업그레이드", // In English: "Upgrade to %strong%" ; Important Translation note: 으 is omitted when it placed after a vowel, meaning: when putting free, pro and business in front of 으로 you need to change it to 로 only (example: "무료로" "프로로" "비즈니스로")
+ "billing.switch_to": "%strong%으로 변경", // In English: "Switch to %strong%", Translation note: same logic from above regarding 으로 applies here too
+ "billing.payment_setup": "결제 설정", // In English: "Payment Setup"
+ "billing.back": "뒤로", // In English: "Back"
+ "billing.you_are_now_subscribed_to":
+ "%strong% 플랜으로 구독이 완료되었습니다.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "구독이 완료되었습니다", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation":
+ "정말 구독을 취소하시겠습니까?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "구독 설정", // In English: "Subscription Setup"
+ "billing.cancel_it": "취소하기", // In English: "Cancel It"
+ "billing.keep_it": "유지하기", // In English: "Keep It"
+ "billing.subscription_resumed": "귀하의 %strong% 구독이 재개되었습니다!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "지금 업그레이드", // In English: "Upgrade Now"
+ "billing.upgrade": "업그레이드", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "현재 무료 플랜을 이용 중입니다.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "영수증 다운로드", // In English: "Download Receipt"
+ "billing.subscription_check_error":
+ "구독 상태를 확인하는 중 문제가 발생했습니다.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed":
+ "이메일이 인증되지 않았습니다. 인증 코드를 보내드리겠습니다.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until":
+ "구독이 취소되었으며, 청구 기간이 끝나면 자동으로 무료 플랜으로 전환됩니다. 구독을 다시 설정할 경우에만 비용이 부과됩니다.", // 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":
+ "청구 기간이 끝날 때까지 유지되는 현재 플랜입니다.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "현재 플랜", // In English: "Current plan" ; depending on the context you could use: "구독 중인 플랜" (plan you are subscribed to)
+
+ "billing.cancelled_subscription_tier": "취소된 구독 (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "관리", // In English: "Manage"
+ "billing.limited": "제한됨", // In English: "Limited"
+ "billing.expanded": "확장됨", // In English: "Expanded"
+ "billing.accelerated": "가속됨", // In English: "Accelerated"
+ "billing.enjoy_msg": "클라우드 저장소 %% 등 다양한 혜택을 즐겨보세요", // In English: "Enjoy %% of Cloud Storage plus other benefits."
},
};
diff --git a/src/gui/src/i18n/translations/ku.js b/src/gui/src/i18n/translations/ku.js
index ee21911f66..962ad7cced 100644
--- a/src/gui/src/i18n/translations/ku.js
+++ b/src/gui/src/i18n/translations/ku.js
@@ -392,6 +392,53 @@ const ku = {
"Owner": "خاوەن", // In English: "Owner"
"You can't share with yourself.": "ناتوانیت لەگەڵ خودی خۆت بڵاوی کەیتەوە", // In English: "You can't share with yourself."
"This user already has access to this item": "ئەم بەکارهێنەرە پێشتر ڕێپێدراوە بۆ ئەم فایلە", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "گۆڕانکاری",
+ "billing.cancel": "بڕینەوە",
+ "billing.download_invoice": "داونلۆد بکە",
+ "billing.payment_method": "شێوازی پارەدان",
+ "billing.payment_method_updated": "شێوازی پارەدان نوێ کراوەتەوە!",
+ "billing.confirm_payment_method": "دروستکردنی شێوازی پارەدان",
+ "billing.payment_history": "مێژووی پارەدان",
+ "billing.refunded": "بەپێچەوانە کراوە",
+ "billing.paid": "پارەی دا",
+ "billing.ok": "باشە",
+ "billing.resume_subscription": "پاشەکەوتی بەردەوام بکە",
+ "billing.subscription_cancelled": "بەژداربوونەکەت هەڵوەشێنراوەتەوە",
+ "billing.subscription_cancelled_description": "تا کۆتایی ئەم ماوەیە تۆ هێشتا دەستت بە بەشداربوونەکەت هەیە",
+ "billing.offering.free": "بە خۆڕایی",
+ "billing.offering.pro": "پیشەیی",
+ "billing.offering.business": "بزنس",
+ "billing.cloud_storage": "خزێنەی هەور",
+ "billing.ai_access": "دەستڕاگەیشتن بە AI",
+ "billing.bandwidth": "باندفیدت",
+ "billing.apps_and_games": "ئەپەکان & یارییەکان",
+ "billing.upgrade_to_pro": "بە %strong% بەرز بکەرەوە",
+ "billing.switch_to": "گۆڕە بۆ %strong%",
+ "billing.payment_setup": "بەکارھێنانی پارەدان",
+ "billing.back": "باک",
+ "billing.you_are_now_subscribed_to": "ئێستا تۆ بەشداریت لە %strong% tier",
+ "billing.you_are_now_subscribed_to_without_tier": "ئێستا تۆ بەشداریت",
+ "billing.subscription_cancellation_confirmation": "ئایا دڵنیایت کە دەتەوێت بەشداربوونەکەت هەڵوەشێنیتەوە؟",
+ "billing.subscription_setup": "دەستکاریی بەشداربوون",
+ "billing.cancel_it": "داوایی لێ بکەوە",
+ "billing.keep_it": "هێشتەوە",
+ "billing.subscription_resumed": "بەژداربوونت %strong% دەستپێکرایەوە!",
+ "billing.upgrade_now": "ئێستا نوێکەرەوە",
+ "billing.upgrade": "Upgrade",
+ "billing.currently_on_free_plan": "ئێستا لە پلانی بێبەرامبەریت",
+ "billing.download_receipt": "دانەوەی وەرگیراو",
+ "billing.subscription_check_error": "کێشەیەک ڕوویدا لەکاتی پشکنینی دۆخی بەشداربوونەکەت",
+ "billing.email_confirmation_needed": " ئیمەیڵەکەت پشتڕاست نەکراوەتەوە. کۆدێکت بۆ دەنێرین بۆ پشتڕاستکردنەوەی ئێستا",
+ "billing.sub_cancelled_but_valid_until": "تۆ بەشداربوونەکەت هەڵوەشاندەوە و بە ئۆتۆماتیکی دەگۆڕێت بۆ پلەی خۆڕایی لە کۆتایی ماوەی فۆڕمی فۆرم. جارێكی دیكە هیچ پارەیەكتان لێناگیرێت مەگەر دووبارە بەشداربن",
+ "billing.current_plan_until_end_of_period": "پلانی ئێستای تۆ تا کۆتایی ئەم ماوەیە بۆ فۆڕمی فۆرم",
+ "billing.current_plan": "پلانی ئێستا",
+ "billing.cancelled_subscription_tier": "بەژمارەی هەڵوەشێندراو (%%) ",
+ "billing.manage": "بەڕێوەبەری",
+ "billing.limited": "Limited",
+ "billing.expanded": "بڵاوکراوەتەوە",
+ "billing.accelerated": "بە خێرایی",
+ "billing.enjoy_msg": "%% لە هەڵگرتنی هەور و سوودی تر وەربگرە"
},
};
diff --git a/src/gui/src/i18n/translations/nb.js b/src/gui/src/i18n/translations/nb.js
index cc6a0d722a..3cb4508697 100644
--- a/src/gui/src/i18n/translations/nb.js
+++ b/src/gui/src/i18n/translations/nb.js
@@ -357,6 +357,54 @@ const nb = {
"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."
+
}
};
diff --git a/src/gui/src/i18n/translations/nl.js b/src/gui/src/i18n/translations/nl.js
index f0a2efc022..950eaaf612 100644
--- a/src/gui/src/i18n/translations/nl.js
+++ b/src/gui/src/i18n/translations/nl.js
@@ -142,14 +142,14 @@ const nl = {
get_copy_link: 'Krijg Kopieerlink',
hide_all_windows: 'Alle Vensters Verbergen',
home: 'Home',
- html_document: 'HTML document',
+ html_document: 'HTML-document',
hue: 'Tint',
image: 'Afbeelding',
incorrect_password: 'Onjuist wachtwoord',
invite_link: 'Uitnodigingslink',
item: 'item',
items_in_trash_cannot_be_renamed: `Dit item kan niet worden hernoemd omdat het in de prullenbak zit. Om dit item te hernoemen, sleept u het eerst uit de prullenbak.`,
- jpeg_image: 'JPEG afbeelding',
+ jpeg_image: 'JPEG-afbeelding',
keep_in_taskbar: 'In Taakbalk Houden',
language: 'Taal',
license: 'Licentie',
@@ -347,20 +347,67 @@ const nl = {
login2fa_recovery_back: 'Terug',
login2fa_recovery_placeholder: 'XXXXXXXX',
- "change": 'Wijzigen', // In English: "Change"
- "clock_visibility": 'Klok zichtbaarheid', // In English: "Clock Visibility"
- "reading": 'Lezen %strong%', // In English: "Reading %strong%"
- "writing": 'Schrijven %strong%', // In English: "Writing %strong%"
- "unzipping": 'Decomprimeren %strong%', // In English: "Unzipping %strong%"
- "sequencing": 'Alles op een rijtje aan het zetten %strong%', // In English: "Sequencing %strong%"
- "zipping": 'Aan het comprimeren %strong%', // In English: "Zipping %strong%"
- "Editor": 'Redacteur', // In English: "Editor"
- "Viewer": 'Kijker', // In English: "Viewer"
- "People with access": 'Mensen met toegang', // In English: "People with access"
- "Share With…": "Deel met...", // In English: "Share With…"
- "Owner": "Eigenaar", // In English: "Owner"
- "You can't share with yourself.": "Je kan niet met jezelf delen.", // In English: "You can't share with yourself."
- "This user already has access to this item": "De gebruiker heeft al toegang tot dit item", // In English: "This user already has access to this item"
+ "change": 'Wijzig',
+ "clock_visibility": 'Klok zichtbaarheid',
+ "reading": 'Lezen %strong%',
+ "writing": 'Schrijven %strong%',
+ "unzipping": 'Decomprimeren %strong%',
+ "sequencing": 'Alles op een rijtje aan het zetten %strong%',
+ "zipping": 'Aan het comprimeren %strong%',
+ "Editor": 'Redacteur',
+ "Viewer": 'Kijker',
+ "People with access": 'Mensen met toegang',
+ "Share With…": 'Deel met...',
+ "Owner": 'Eigenaar',
+ "You can't share with yourself.": 'Je kan niet met jezelf delen.',
+ "This user already has access to this item": 'De gebruiker heeft al toegang tot dit item',
+
+ "billing.change_payment_method": 'Wijzig', // In English: 'Change"
+ "billing.cancel": 'Annuleer', // In English: 'Cancel"
+ "billing.download_invoice": 'Download', // In English: 'Download"
+ "billing.payment_method": 'Betaalmethode', // In English: 'Payment Method"
+ "billing.payment_method_updated": 'Betaalmethode bijgewerkt!', // In English: 'Payment method updated!"
+ "billing.confirm_payment_method": 'Bevestig Betaalmethode', // In English: 'Confirm Payment Method"
+ "billing.payment_history": 'Betaalgeschiedenis', // In English: 'Payment History"
+ "billing.refunded": 'Terugbetaald', // In English: 'Refunded"
+ "billing.paid": 'Betaald', // In English: 'Paid"
+ "billing.ok": 'OK', // In English: 'OK"
+ "billing.resume_subscription": 'Zet Abonnement voort', // In English: 'Resume Subscription"
+ "billing.subscription_cancelled": 'Uw abonnement is stopgezet.', // In English: 'Your subscription has been canceled."
+ "billing.subscription_cancelled_description": 'U behoudt toegang tot uw abonnement tot het einde van deze factureringsperiode.', // 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": 'Professioneel', // In English: 'Professional"
+ "billing.offering.business": 'Bedrijf', // In English: 'Business"
+ "billing.cloud_storage": 'Cloudopslag', // In English: 'Cloud Storage"
+ "billing.ai_access": 'AI Toegang', // In English: 'AI Access"
+ "billing.bandwidth": 'Bandbreedte', // In English: 'Bandwidth"
+ "billing.apps_and_games": 'Apps & Spelletjes', // In English: 'Apps & Games"
+ "billing.upgrade_to_pro": 'Upgraden naar %strong%', // In English: 'Upgrade to %strong%"
+ "billing.switch_to": 'Wissel naar %strong%', // In English: 'Switch to %strong%"
+ "billing.payment_setup": 'Betaal', // In English: 'Payment Setup"
+ "billing.back": 'Terug', // In English: 'Back"
+ "billing.you_are_now_subscribed_to": 'U bent nu geabonneerd op de %strong% rang.', // In English: 'You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": 'U bent nu geabonneerd', // In English: 'You are now subscribed"
+ "billing.subscription_cancellation_confirmation": 'Bent u zeker dat u uw abonnement wilt stopzetten?', // In English: 'Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": 'Abonnement Instellen ', // In English: 'Subscription Setup"
+ "billing.cancel_it": 'Annuleer het', // In English: 'Cancel It"
+ "billing.keep_it": 'Hou het', // In English: 'Keep It"
+ "billing.subscription_resumed": 'Uw %strong% abonnement is voortgezet!', // In English: 'Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": 'Upgrade Nu', // In English: 'Upgrade Now"
+ "billing.upgrade": 'Upgrade', // In English: 'Upgrade"
+ "billing.currently_on_free_plan": 'U hebt momenteel het gratis abonnement.', // In English: 'You are currently on the free plan."
+ "billing.download_receipt": 'Download Ontvangstbewijs', // In English: 'Download Receipt"
+ "billing.subscription_check_error": 'Er is een probleem opgetreden bij het controleren van uw abonnementsstatus.', // In English: 'A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": 'Uw e-mail is nog niet bevestigd. We zullen u een code sturen om het te bevestigen.', // In English: 'Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": 'U hebt uw abonnement stopgezet en het zal automatisch overschakelen naar het gratis abonnement op het einde van deze factureringsperiode. Er worden geen kosten in rekening gebracht, tenzij u opnieuw abonneert.', // 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": 'Uw huidige abonnement tot het einde van deze factureringsperiode.', // In English: 'Your current plan until the end of this billing period."
+ "billing.current_plan": 'Huidige Abonnement', // In English: 'Current plan"
+ "billing.cancelled_subscription_tier": 'Stop Abonnement', // In English: 'Cancelled Subscription (%%)"
+ "billing.manage": 'Beheer', // In English: 'Manage"
+ "billing.limited": 'Beperkt', // In English: 'Limited"
+ "billing.expanded": 'Uitgebreid', // In English: 'Expanded"
+ "billing.accelerated": 'Versneld', // In English: 'Accelerated"
+ "billing.enjoy_msg": 'Geniet %% van Cloudopslag en meer voordelen.', // In English: 'Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/nn.js b/src/gui/src/i18n/translations/nn.js
index 566b03b42c..ab8e1e7f8c 100644
--- a/src/gui/src/i18n/translations/nn.js
+++ b/src/gui/src/i18n/translations/nn.js
@@ -163,9 +163,9 @@ const nn = {
you_have_been_referred_to_puter_by_a_friend: "Du har blitt referert til Puter av ein ven!",
zip: "Zip",
- // ***********************************
- // Missing translations
- // ***********************************
+ // ----------------------------------------
+ // Missing translations:
+ // ----------------------------------------
"about": undefined, // In English: "About"
"account": undefined, // In English: "Account"
"account_password": undefined, // In English: "Verify Account Password"
@@ -336,7 +336,7 @@ const nn = {
"setup2fa_3_step_heading": undefined, // In English: "Enter the 6-digit code"
"setup2fa_4_step_heading": undefined, // In English: "Copy your recovery codes"
"setup2fa_4_instructions": undefined, // In English: "
- // These recovery codes are the only way to access your account if you lose your phone or can't use your authenticator app.
+ // These recovery codes are the only way to access your account if you lose your phone or can\'t use your authenticator app.
// Make sure to store them in a safe place.
// "
"setup2fa_5_step_heading": undefined, // In English: "Confirm 2FA setup"
@@ -357,6 +357,52 @@ const nn = {
"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."
}
};
diff --git a/src/gui/src/i18n/translations/pl.js b/src/gui/src/i18n/translations/pl.js
index c1f32c8ea7..6e89438f00 100644
--- a/src/gui/src/i18n/translations/pl.js
+++ b/src/gui/src/i18n/translations/pl.js
@@ -361,6 +361,54 @@ const pl = {
"Owner": 'właściciel',
"You can't share with yourself.": 'Nie możesz dzielić się z samym sobą',
"This user already has access to this item": 'Ten użytkownik ma już dostęp do tego elementu',
+
+ "plural_suffix": '', // Leaving it empty, as the previous translator did
+ "billing.change_payment_method": 'Zmień',
+ "billing.cancel": 'Anuluj',
+ "billing.download_invoice": 'Pobierz',
+ "billing.payment_method": 'Metoda Płatności',
+ "billing.payment_method_updated": 'Metoda płatności została zaktualizowana!',
+ "billing.confirm_payment_method": 'Potwierdź Metodę Płatności',
+ "billing.payment_history": 'Historia Płatności',
+ "billing.refunded": 'Zwrócono',
+ "billing.paid": 'Opłacono',
+ "billing.ok": 'OK',
+ "billing.resume_subscription": 'Wznów Subskrypcję',
+ "billing.subscription_cancelled": 'Twoja subskrypcja została anulowana',
+ "billing.subscription_cancelled_description": 'Dostęp do Twojej subskrypcji będzie możliwy do końca tego okresu rozliczeniowego.',
+ "billing.offering.free": 'Darmowy',
+ "billing.offering.pro": 'Pro',
+ "billing.offering.business": 'Dla Firm',
+ "billing.cloud_storage": 'Pamięć w Chmurze',
+ "billing.ai_access": 'Możliwość Dostępu do AI',
+ "billing.bandwidth": 'Przepustowość',
+ "billing.apps_and_games": 'Aplikacji i Gier', // "1000s of Apps & Games"
+ "billing.upgrade_to_pro": 'Rozszerz na plan %strong%',
+ "billing.switch_to": 'Zmień na plan %strong%',
+ "billing.payment_setup": 'Ustawienia Płatności',
+ "billing.back": 'Wstecz',
+ "billing.you_are_now_subscribed_to": 'Obecnie subskrybujesz plan %strong%',
+ "billing.you_are_now_subscribed_to_without_tier": 'Jesteś teraz subskrybentem',
+ "billing.subscription_cancellation_confirmation": 'Czy na pewno chcesz anulować subskrypcję?',
+ "billing.subscription_setup": 'Ustawienia Subskrypcji',
+ "billing.cancel_it": 'Anuluj To',
+ "billing.keep_it": 'Zachowaj To',
+ "billing.subscription_resumed": 'Twoja subskrypcja obejmująca plan %strong% została wznowiona',
+ "billing.upgrade_now": 'Ulepsz Teraz',
+ "billing.upgrade": 'Ulepsz',
+ "billing.currently_on_free_plan": 'Obecnie korzystasz z darmowego planu.',
+ "billing.download_receipt": 'Pobierz Potwierdzenie',
+ "billing.subscription_check_error": 'Wystąpił błąd podczas sprawdzania stanu Twojej subskrypcji.',
+ "billing.email_confirmation_needed": 'Twój adres e-mail nie został potwierdzony. Wyślemy Ci kod, aby potwierdzić go teraz.',
+ "billing.sub_cancelled_but_valid_until": 'Twoja subskrypcja została anulowana i po zakończeniu okresu rozliczeniowego zostanie automatycznie zmieniona na plan darmowy. Opłata nie zostanie naliczona ponownie, dopóki nie dokonasz ponownej subskrypcji.',
+ "billing.current_plan_until_end_of_period": 'Twój obecny plan do końca tego okresu rozliczeniowego.',
+ "billing.current_plan": 'Twój obecny plan',
+ "billing.cancelled_subscription_tier": 'Anulowana Subskrypcja (%%)',
+ "billing.manage": 'Zarządzaj',
+ "billing.limited": 'Ograniczona', // \
+ "billing.expanded": 'Rozszerzona', // -> These adjectives are used to describe the "AI Access" and/or "Bandwidth"
+ "billing.accelerated": 'Przyspieszona', // /
+ "billing.enjoy_msg": 'Ciesz się pakietem %% pamięci w chmurze i innymi benefitami',
}
};
diff --git a/src/gui/src/i18n/translations/pt.js b/src/gui/src/i18n/translations/pt.js
index e11360f30c..eed6d0a886 100644
--- a/src/gui/src/i18n/translations/pt.js
+++ b/src/gui/src/i18n/translations/pt.js
@@ -358,7 +358,62 @@ const pt = {
'Share_With': 'Partilhar com…', // In English: "Share With…"
'Owner': 'Administrador', // In English: "Owner"
'You_cant_share_with_yourself': 'Não podes partilhar contigo mesmo', // In English: "You can't share with yourself."
- 'This_user_already_has_access_to_this_item': 'Este utilizador já tem acesso a este item' // In English: "This user already has access to this item"
+ 'This_user_already_has_access_to_this_item': 'Este utilizador já tem acesso a este item', // In English: "This user already has access to this item"
+
+ // ----------------------------------------
+ // Missing translations:
+ // ----------------------------------------
+ "People with access": "Pessoas com acesso", // In English: "People with access"
+ "Share With…": "Partilhar com…", // In English: "Share With…"
+ "You can't share with yourself.": "Não pode partilhar consigo mesmo.", // In English: "You can't share with yourself."
+ "This user already has access to this item": "Este utilizador já tem acesso a este item.", // In English: "This user already has access to this item"
+ "billing.change_payment_method": "Alterar", // In English: "Change"
+ "billing.cancel": "Cancelar", // In English: "Cancel"
+ "billing.download_invoice": "Descarregar", // In English: "Download"
+ "billing.payment_method": "Método de Pagamento", // In English: "Payment Method"
+ "billing.payment_method_updated": "Método de pagamento atualizado!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Confirmar Método de Pagamento", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Histórico de Pagamentos", // In English: "Payment History"
+ "billing.refunded": "Reembolsado", // In English: "Refunded"
+ "billing.paid": "Pago", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Retomar Subscrição", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "A sua subscrição foi cancelada.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Ainda terá acesso à sua subscrição até ao final deste período de faturação.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Grátis", // In English: "Free"
+ "billing.offering.pro": "Profissional", // In English: "Professional"
+ "billing.offering.business": "Empresarial", // In English: "Business"
+ "billing.cloud_storage": "Armazenamento na Nuvem", // In English: "Cloud Storage"
+ "billing.ai_access": "Acesso à IA", // In English: "AI Access"
+ "billing.bandwidth": "Largura de Banda", // In English: "Bandwidth"
+ "billing.apps_and_games": "Aplicações e Jogos", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Atualizar para %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Trocar para %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Configuração de Pagamento", // In English: "Payment Setup"
+ "billing.back": "Voltar", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "A sua subscrição no nivel %strong% foi realizada com uscesso.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "A sua subscrição foi realizada com sucesso", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Tem a certeza de que deseja cancelar a sua subscrição?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Configuração da Subscrição", // In English: "Subscription Setup"
+ "billing.cancel_it": "Cancelar", // In English: "Cancel It"
+ "billing.keep_it": "Manter", // In English: "Keep It"
+ "billing.subscription_resumed": "A sua subscrição %strong% foi reativada!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Atualizar Agora", // In English: "Upgrade Now"
+ "billing.upgrade": "Atualizar", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Está atualmente no plano gratuito.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Descarregar Recibo", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Ocorreu um problema ao verificar o estado da sua subscrição.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "O seu email não foi confirmado. Enviaremos agora um código para o confirmar.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Cancelou a sua subscrição e será automaticamente ativado o plano gratuito no final do período de faturação. Não será cobrado novamente a menos que volte a subscrever.", // 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": "O seu plano atual até ao final deste período de faturação.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Plano Atual", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Subscrição Cancelada (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Gerir", // In English: "Manage"
+ "billing.limited": "Limitado", // In English: "Limited"
+ "billing.expanded": "Expandido", // In English: "Expanded"
+ "billing.accelerated": "Acelerado", // In English: "Accelerated"
+ "billing.enjoy_msg": "Desfrute de %% de Armazenamento na Nuvem e outros benefícios.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
+
}
};
diff --git a/src/gui/src/i18n/translations/ro.js b/src/gui/src/i18n/translations/ro.js
index 8b6870c1e2..ab93c061a7 100644
--- a/src/gui/src/i18n/translations/ro.js
+++ b/src/gui/src/i18n/translations/ro.js
@@ -359,6 +359,52 @@ const ro = {
"You can't share with yourself.": "Nu poți partaja cu tine însuți.", // In English: "You can't share with yourself."
"This user already has access to this item": "Acest utilizator are deja acces la acest element",
+ "billing.change_payment_method": "Schimbă",
+ "billing.cancel": "Anulează",
+ "billing.download_invoice": "Descarcă",
+ "billing.payment_method": "Metodă de plată",
+ "billing.payment_method_updated": "Metoda de plată a fost actualizată!",
+ "billing.confirm_payment_method": "Confirmă metoda de plată",
+ "billing.payment_history": "Istoric plăți",
+ "billing.refunded": "Rambursat",
+ "billing.paid": "Plătit",
+ "billing.ok": "OK",
+ "billing.resume_subscription": "Reactivează abonamentul",
+ "billing.subscription_cancelled": "Abonamentul tău a fost anulat.",
+ "billing.subscription_cancelled_description": "Vei avea în continuare acces la abonament până la sfârșitul perioadei de facturare actuale.",
+ "billing.offering.free": "Gratis",
+ "billing.offering.pro": "Profesional",
+ "billing.offering.business": "Business", // Keeping "Business" as it's commonly used in Romanian
+ "billing.cloud_storage": "Stocare în cloud",
+ "billing.ai_access": "Acces AI",
+ "billing.bandwidth": "Lățime de bandă",
+ "billing.apps_and_games": "Aplicații și jocuri",
+ "billing.upgrade_to_pro": "Actualizează la %strong%",
+ "billing.switch_to": "Schimbă la %strong%",
+ "billing.payment_setup": "Configurare plată",
+ "billing.back": "Înapoi",
+ "billing.you_are_now_subscribed_to": "Acum ești abonat la nivelul %strong%.",
+ "billing.you_are_now_subscribed_to_without_tier": "Acum ești abonat",
+ "billing.subscription_cancellation_confirmation": "Ești sigur că vrei să anulezi abonamentul?",
+ "billing.subscription_setup": "Configurare abonament",
+ "billing.cancel_it": "Anulează-l",
+ "billing.keep_it": "Păstrează-l",
+ "billing.subscription_resumed": "Abonamentul tău %strong% a fost reactivat!",
+ "billing.upgrade_now": "Actualizează acum",
+ "billing.upgrade": "Actualizează",
+ "billing.currently_on_free_plan": "În prezent folosești planul gratuit.",
+ "billing.download_receipt": "Descarcă chitanța",
+ "billing.subscription_check_error": "A apărut o problemă la verificarea abonamentului.",
+ "billing.email_confirmation_needed": "E-mailul tău nu a fost confirmat. Îți vom trimite acum un cod de confirmare.",
+ "billing.sub_cancelled_but_valid_until": "Ți-ai anulat abonamentul și va trece automat la planul gratuit la sfârșitul perioadei de facturare. Nu vei mai fi taxat decât dacă te reabonezi.",
+ "billing.current_plan_until_end_of_period": "Planul tău actual până la sfârșitul perioadei de facturare actuale.",
+ "billing.current_plan": "Plan actual",
+ "billing.cancelled_subscription_tier": "Abonament anulat (%%)",
+ "billing.manage": "Administrează",
+ "billing.limited": "Limitat",
+ "billing.expanded": "Extins",
+ "billing.accelerated": "Accelerat",
+ "billing.enjoy_msg": "Bucură-te de %% spațiu de stocare în cloud plus alte beneficii.",
}
}
diff --git a/src/gui/src/i18n/translations/ru.js b/src/gui/src/i18n/translations/ru.js
index bf794daf15..38900f7c47 100644
--- a/src/gui/src/i18n/translations/ru.js
+++ b/src/gui/src/i18n/translations/ru.js
@@ -397,8 +397,54 @@ const ru = {
'Share With…': 'Поделиться с...',
Owner: 'Владелец',
"You can't share with yourself.": 'Вы не можете поделиться с самим собой.',
- 'This user already has access to this item':
- 'Этот пользователь уже имеет доступ к этому элементу.',
+ 'This user already has access to this item': 'Этот пользователь уже имеет доступ к этому элементу.',
+
+ "billing.change_payment_method": 'Изменить', // In English: "Change"
+ "billing.cancel": 'Отмена', // In English: "Cancel"
+ "billing.download_invoice": 'Загрузить', // In English: "Download"
+ "billing.payment_method": 'Метод оплаты', // In English: "Payment Method"
+ "billing.payment_method_updated": 'Метод оплаты обновлён!', // In English: "Payment method updated!"
+ "billing.confirm_payment_method": 'Подтвердить метод оплаты', // In English: "Confirm Payment Method"
+ "billing.payment_history": 'История платежей', // In English: "Payment History"
+ "billing.refunded": 'Средства возвращены', // In English: "Refunded"
+ "billing.paid": 'Оплачено', // In English: "Paid"
+ "billing.ok": 'Ок', // In English: "OK"
+ "billing.resume_subscription": 'Продолжить подписку', // In English: "Resume Subscription"
+ "billing.subscription_cancelled": 'Ваша подписка отменена.', // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": 'Вы можете пользоваться подпиской до конца оплаченного периода', // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": 'Бесплатно', // In English: "Free"
+ "billing.offering.pro": 'Профессиональная', // In English: "Professional"
+ "billing.offering.business": 'Бизнес', // In English: "Business"
+ "billing.cloud_storage": 'Облачное хранилище', // In English: "Cloud Storage"
+ "billing.ai_access": 'Доступ к ИИ', // In English: "AI Access"
+ "billing.bandwidth": 'Пропускная способность', // In English: "Bandwidth"
+ "billing.apps_and_games": 'Игры и приложения', // In English: "Apps & Games"
+ "billing.upgrade_to_pro": 'Обновить до %strong%', // In English: "Upgrade to %strong%"
+ "billing.switch_to": 'Переключиться на %strong%', // In English: "Switch to %strong%"
+ "billing.payment_setup": 'Настройки оплаты', // In English: "Payment Setup"
+ "billing.back": 'Назад', // In English: "Back"
+ "billing.you_are_now_subscribed_to": 'Теперь Ваш уровень подписки %strong%.', // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": 'Теперь Вы подписаны', // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": 'Вы уверены, что хотите отменить Вашу подписку?', // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": 'Настройки подписки', // In English: "Subscription Setup"
+ "billing.cancel_it": 'Отменить', // In English: "Cancel It"
+ "billing.keep_it": 'Удержать', // In English: "Keep It"
+ "billing.subscription_resumed": 'Ваша %strong% подписка была продлена!', // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": 'Обновить сейчас', // In English: "Upgrade Now"
+ "billing.upgrade": 'Обновить', // In English: "Upgrade"
+ "billing.currently_on_free_plan": 'Сейчас у Вас бесплатный план.', // In English: "You are currently on the free plan."
+ "billing.download_receipt": 'Загрузить квитанцию', // In English: "Download Receipt"
+ "billing.subscription_check_error": 'Произошла ошибка при проверке статуса Вашей подписки.', // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": 'Ваш e-mail не подтверждён. Мы отправили Вам код для подтверждения.', // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": 'Вы отменили Вашу подписку и она автоматически переключится на бесплатный план по истечению оплаченного периода. С Вас не будет взыматься плата, если Вы не подпишетесь повторно', // 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": 'Ваш действующий план до конца оплаченного периода', // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": 'Настоящий план', // In English: "Current plan"
+ "billing.cancelled_subscription_tier": 'Отменённая подписка (%%)', // In English: "Cancelled Subscription (%%)"
+ "billing.manage": 'Управление', // In English: "Manage"
+ "billing.limited": 'Ограничено', // In English: "Limited"
+ "billing.expanded": 'Расширенный', // In English: "Expanded"
+ "billing.accelerated": 'Ускоренный', // In English: "Accelerated"
+ "billing.enjoy_msg": 'Пользуйтесь %% Облачным Хранилищем и остальными выгодами', // In English: "Enjoy %% of Cloud Storage plus other benefits."
},
}
diff --git a/src/gui/src/i18n/translations/sv.js b/src/gui/src/i18n/translations/sv.js
index ad4d636d38..6c3e1e9489 100644
--- a/src/gui/src/i18n/translations/sv.js
+++ b/src/gui/src/i18n/translations/sv.js
@@ -348,22 +348,69 @@ const sv = {
login2fa_recovery_placeholder: "XXXXXXXX",
"change": "Ändra", // In English: "Change"
- "clock_visibility": "Klocksynlighet", // In English: "Clock Visibility"
- "plural_suffix": "", // In English: "s" (Plural suffix is context dependent in Swedish, it can be "or", "ar", "er", "en" or just no suffix)
- "reading": "Läser %strong%", // In English: "Reading %strong%"
- "writing": "Skriver %strong%", // In English: "Writing %strong%"
- "unzipping": "Packar upp %strong%", // In English: "Unzipping %strong%"
- "sequencing": "Sekvenserar %strong%", // In English: "Sequencing %strong%"
- "zipping": "Komprimerar %strong%", // In English: "Zipping %strong%"
- "Editor": "Redigerare", // In English: "Editor"
- "Viewer": "Granskare", // In English: "Viewer"
- "People with access": "Personer med åtkomst", // In English: "People with access"
- "Share With…": "Dela med…", // In English: "Share With…"
- "Owner": "Ägare", // In English: "Owner"
- "You can't share with yourself.": "Du kan inte dela med dig själv.", // In English: "You can't share with yourself."
- "This user already has access to this item":
- "Den här användaren har redan åtkomst till det här objektet", // In English: "This user already has access to this item"
+ "clock_visibility": "Klocksynlighet", // In English: "Clock Visibility"
+ "plural_suffix": "", // In English: "s" (Plural suffix is context dependent in Swedish, it can be "or", "ar", "er", "en" or just no suffix)
+ "reading": "Läser %strong%", // In English: "Reading %strong%"
+ "writing": "Skriver %strong%", // In English: "Writing %strong%"
+ "unzipping": "Packar upp %strong%", // In English: "Unzipping %strong%"
+ "sequencing": "Sekvenserar %strong%", // In English: "Sequencing %strong%"
+ "zipping": "Komprimerar %strong%", // In English: "Zipping %strong%"
+ "Editor": "Redigerare", // In English: "Editor"
+ "Viewer": "Granskare", // In English: "Viewer"
+ "People with access": "Personer med åtkomst", // In English: "People with access"
+ "Share With…": "Dela med…", // In English: "Share With…"
+ "Owner": "Ägare", // In English: "Owner"
+ "You can't share with yourself.": "Du kan inte dela med dig själv.", // In English: "You can't share with yourself."
+ "This user already has access to this item":
+ "Den här användaren har redan åtkomst till det här objektet", // In English: "This user already has access to this item"
+ "plural_suffix": "", // In English: "s" (Plural suffix is context dependent in Swedish, it can be "or", "ar", "er", "en" or just no suffix)
+ "billing.change_payment_method": "Ändra", // In English: "Change"
+ "billing.cancel": "Avbryt", // In English: "Cancel"
+ "billing.download_invoice": "Ladda ner", // In English: "Download"
+ "billing.payment_method": "Betalningsmetod", // In English: "Payment Method"
+ "billing.payment_method_updated": "Betalningsmetod uppdaterad!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Bekräfta Betalningsmetod", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Betalningshistorik", // In English: "Payment History"
+ "billing.refunded": "Återbetalas", // In English: "Refunded"
+ "billing.paid": "Betalt", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Återuppta Prenumeration", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Din prenumeration har avbrutits.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Du har fortfarande tillgång till din prenumeration fram till slutet av denna faktureringsperiod.", // 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": "Professionell", // In English: "Professional"
+ "billing.offering.business": "Företag", // In English: "Business"
+ "billing.cloud_storage": "Molnlagring", // In English: "Cloud Storage"
+ "billing.ai_access": "AI Tillgång", // In English: "AI Access"
+ "billing.bandwidth": "Bandbredd", // In English: "Bandwidth"
+ "billing.apps_and_games": "Appar & Spel", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Uppgradera till %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Byt till %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Betalningsinställningar", // In English: "Payment Setup"
+ "billing.back": "Tillbaka", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Du prenumererar nu på %strong% tier.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Du är nu prenumererad", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Är du säker på att du vill avsluta din prenumeration?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Prenumerationsinställningar", // In English: "Subscription Setup"
+ "billing.cancel_it": "Avbryt det", // In English: "Cancel It"
+ "billing.keep_it": "Behåll det", // In English: "Keep It"
+ "billing.subscription_resumed": "Din %strong% prenumeration har återupptagits!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Uppgradera nu", // In English: "Upgrade Now"
+ "billing.upgrade": "Uppgradera", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Du har för närvarande den kostnadsfria planen.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Ladda ner Kvitto", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Ett problem uppstod när du kontrollerade din prenumerationsstatus.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Din e-post har inte bekräftats. Vi skickar dig en kod för att bekräfta den nu.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Du har sagt upp din prenumeration och den byter automatiskt till gratisnivån i slutet av faktureringsperioden. Du kommer inte att debiteras igen om du inte prenumererar på nytt.", // 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 nuvarande plan fram till slutet av denna faktureringsperiod.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Nuvarande plan", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Avbruten Prenumeration (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Hantera", // In English: "Manage"
+ "billing.limited": "Begränsad", // In English: "Limited"
+ "billing.expanded": "Utökad", // In English: "Expanded"
+ "billing.accelerated": "Accelererad", // In English: "Accelerated"
+ "billing.enjoy_msg": "Njut av %% av Cloud Storage plus andra förmåner.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/ta.js b/src/gui/src/i18n/translations/ta.js
index ddb0b048ed..ebd7d244ba 100644
--- a/src/gui/src/i18n/translations/ta.js
+++ b/src/gui/src/i18n/translations/ta.js
@@ -360,6 +360,56 @@ const ta = {
"Owner": 'உரிமையாளர்', // In English: "Owner"
"You can't share with yourself.": 'உங்களுடன் பகிர்ந்து கொள்ள முடியாது', // In English: "You can't share with yourself."
"This user already has access to this item": 'இந்தப் பயனருக்கு ஏற்கனவே இந்த உருப்படிக்கான அணுகல் உள்ளது', // In English: "This user already has access to this item"
+
+ // ----------------------------------------
+ // Missing translations:
+ // ----------------------------------------
+ "billing.change_payment_method": "மாற்று", // In English: "Change"
+ "billing.cancel": "ரத்து செய்", // In English: "Cancel"
+ "billing.download_invoice": "பதிவிறக்கு", // In English: "Download"
+ "billing.payment_method": "பணம் செலுத்தும் முறை", // In English: "Payment Method"
+ "billing.payment_method_updated": "பணம் செலுத்தும் முறை புதுப்பிக்கப்பட்டது!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "பணம் செலுத்தும் முறையை உறுதிப்படுத்து", // In English: "Confirm Payment Method"
+ "billing.payment_history": "பணம் செலுத்திய வரலாறு", // In English: "Payment History"
+ "billing.refunded": "திரும்பப் பெற்றது", // In English: "Refunded"
+ "billing.paid": "செலுத்தப்பட்டது", // In English: "Paid"
+ "billing.ok": "சரி", // In English: "OK"
+ "billing.resume_subscription": "சப்ஸ்கிரிப்ஷன் மீண்டும் தொடங்கு", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "உங்கள் சப்ஸ்கிரிப்ஷன் ரத்து செய்யப்பட்டு விட்டது.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "நீங்கள் இந்த பில்லிங் காலத்தின் முடிவுவரை உங்கள் சப்ஸ்கிரிப்ஷனுக்கு அணுகல் பெறுவீர்கள்.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "இலவசம்", // In English: "Free"
+ "billing.offering.pro": "தொழில்முறை", // In English: "Professional"
+ "billing.offering.business": "வியாபாரம்", // In English: "Business"
+ "billing.cloud_storage": "மேக சேமிப்பு", // In English: "Cloud Storage"
+ "billing.ai_access": "AI அணுகல்", // In English: "AI Access"
+ "billing.bandwidth": "அலைவரிசை", // In English: "Bandwidth"
+ "billing.apps_and_games": "ஆப்ஸ் & கேம்ஸ்", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "%strong%க்கு மேம்படுத்தவும்", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "%strong% இதற்கு மாறவும்", // In English: "Switch to %strong%"
+ "billing.payment_setup": "கட்டண அமைப்பு", // In English: "Payment Setup"
+ "billing.back": "திரும்ப", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "நீங்கள் இப்போது %strong% அடுக்குக்கு குழுசேர்ந்துள்ளீர்கள்.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "நீங்கள் இப்போது குழுசேர்ந்துள்ளீர்கள்", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "உங்கள் சந்தாவை நிச்சயமாக ரத்துசெய்ய விரும்புகிறீர்களா?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "சந்தா அமைப்பு", // In English: "Subscription Setup"
+ "billing.cancel_it": "ரத்து செய்", // In English: "Cancel It"
+ "billing.keep_it": "வைத்துக்கொள்", // In English: "Keep It"
+ "billing.subscription_resumed": "உங்கள் %strong% சந்தா மீண்டும் தொடங்கப்பட்டது!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "இப்போது மேம்படுத்து", // In English: "Upgrade Now"
+ "billing.upgrade": "மேம்படுத்து", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "நீங்கள் தற்போது இலவச திட்டத்தில் உள்ளீர்கள்.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "ரசீதைப் பதிவிறக்கவும்", // In English: "Download Receipt"
+ "billing.subscription_check_error": "உங்கள் சந்தா நிலையைச் சரிபார்க்கும் போது சிக்கல் ஏற்பட்டது.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "உங்கள் மின்னஞ்சல் உறுதிப்படுத்தப்படவில்லை. இப்போது அதை உறுதிப்படுத்த ஒரு குறியீட்டை அனுப்புவோம்.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "உங்கள் சந்தாவை ரத்து செய்துவிட்டீர்கள், பில்லிங் காலத்தின் முடிவில் அது தானாகவே இலவச அடுக்குக்கு மாறும். நீங்கள் மீண்டும் சந்தா செலுத்தும் வரை உங்களிடம் கட்டணம் வசூலிக்கப்படாது.", // 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": "இந்த பில்லிங் காலம் முடியும் வரை உங்களின் தற்போதைய திட்டம்.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "தற்போதைய திட்டம்", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "ரத்துசெய்யப்பட்ட சந்தா (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "நிர்வகிக்கவும்", // In English: "Manage"
+ "billing.limited": "வரையறுக்கப்பட்டவை", // In English: "Limited"
+ "billing.expanded": "விரிவாக்கப்பட்டது", // In English: "Expanded"
+ "billing.accelerated": "வேகப்படுத்தப்பட்டது", // In English: "Accelerated"
+ "billing.enjoy_msg": "%% கிளவுட் ஸ்டோரேஜ் மற்றும் பிற பலன்களை அனுபவிக்கவும்.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/th.js b/src/gui/src/i18n/translations/th.js
index 2bcb7d9d77..ab63765c65 100644
--- a/src/gui/src/i18n/translations/th.js
+++ b/src/gui/src/i18n/translations/th.js
@@ -363,7 +363,53 @@ const th = {
"You can't share with yourself.": 'คุณไม่สามารถแบ่งปันกับตัวเองได้', // In English: "You can't share with yourself."
"This user already has access to this item": 'ผู้ใช้นี้สามารถเข้าถึงรายการนี้ได้แล้ว', // In English: "This user already has access to this item"
+ "billing.change_payment_method": "เปลี่ยน", // In English: "Change"
+ "billing.cancel": "ยกเลิก", // In English: "Cancel"
+ "billing.download_invoice": "ดาวน์โหลด", // In English: "Download"
+ "billing.payment_method": "วิธีการชำระเงิน", // In English: "Payment Method"
+ "billing.payment_method_updated": "เปลี่ยนแปลงวิธีการชำระเงินสำเร็จ", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "ยืนยันวิธีการชำระเงิน", // In English: "Confirm Payment Method"
+ "billing.payment_history": "ประวัติการชำระเงิน", // In English: "Payment History"
+ "billing.refunded": "คืนเงินสำเร็จ", // In English: "Refunded"
+ "billing.paid": "จ่ายแล้ว", // In English: "Paid"
+ "billing.ok": "ตกลง", // In English: "OK"
+ "billing.resume_subscription": "ต่ออายุสมาชิก", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "สมาชิกของคุณถูกยกเลิกแล้ว", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "คุณยังเป็นสมาชิกอยู่จนถึงวันสิ้นสุดรอบบิลนี้", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "ฟรี", // In English: "Free"
+ "billing.offering.pro": "มืออาชีพ", // In English: "Professional"
+ "billing.offering.business": "ธุรกิจ", // In English: "Business"
+ "billing.cloud_storage": "จัดเก็บบนคลาวด์", // In English: "Cloud Storage"
+ "billing.ai_access": "การเข้าถึงโดย AI", // In English: "AI Access"
+ "billing.bandwidth": "แบนด์วิดท์", // In English: "Bandwidth"
+ "billing.apps_and_games": "แอป และ เกมส์", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "เพิ่ม %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "เปลี่ยนเป็น %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "ตั้งค่าการชำระเงิน", // In English: "Payment Setup"
+ "billing.back": "ย้อนกลับ", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "คุณได้สมัครเป็นระดับ %strong แล้ว", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "คุณเป็นสมาชิกใหม่แล้ว", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "คุณแน่ใจที่จะยกเลิกการเป็นสมาชิกหรือไม่?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "การตั้งค่าการเป็นสมาชิก", // In English: "Subscription Setup"
+ "billing.cancel_it": "ยกเลิก", // In English: "Cancel It"
+ "billing.keep_it": "เก็บไว้", // In English: "Keep It"
+ "billing.subscription_resumed": "คุณได้กลับคืนสู่การเป็นสมาชิกระดับ %strong%", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "อัพเกรดตอนนี้", // In English: "Upgrade Now"
+ "billing.upgrade": "อัพเกรด", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "คุณเป็นสมาชิกระดับฟรีในตอนนี้", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "ดาวน์โหลดใบเสร็จ", // In English: "Download Receipt"
+ "billing.subscription_check_error": "มีปัญหาในการตรวจสอบสถานะสมาชิกของคุณ", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "อีเมล์ของคุณยังไม่ได้รับการยืนยัน เราจะส่งโค้ดไปทางอีเมล์ของคุณเพื่อทำการยืนยันตอนนี้", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "สมาชิกของคุณได้ถูกยกเลิกแล้วและจะถูกเปลี่ยนเป็นระดับฟรีหลังจากสิ้นสุดรอบบิลนี้ จะไม่มีการเรียกเก็บค่าใช้จ่ายหลังจากนี้ ยกเว้นกรณีสมัครสมาชิกใหม่", // 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": "ระดับสมาชิกของคุณจนกว่าจะสิ้นสุดรอบบิลนี้", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "ระดับสมาชิกปัจจุบัน", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "สมาชิกที่ถูกยกเลิกไปแล้ว (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "จัดการ", // In English: "Manage"
+ "billing.limited": "จำกัด", // In English: "Limited"
+ "billing.expanded": "ขยาย", // In English: "Expanded"
+ "billing.accelerated": "เร่ง", // In English: "Accelerated"
+ "billing.enjoy_msg": "ขอให้สนุกกับพื้นที่จัดเก็บบนคลาด์ที่เพิ่มขึ้น %% ของคุณ", // In English: "Enjoy "" of Cloud Storage plus other benefits."
}
};
-export default th;
\ No newline at end of file
+export default th;
diff --git a/src/gui/src/i18n/translations/tr.js b/src/gui/src/i18n/translations/tr.js
index 163e04393b..7cd913a2c6 100644
--- a/src/gui/src/i18n/translations/tr.js
+++ b/src/gui/src/i18n/translations/tr.js
@@ -365,7 +365,54 @@ const tr = {
"Owner": "Sahip", // In English: "Owner"
"You can't share with yourself.": "Kendinizle paylaşamazsınız.", // In English: "You can't share with yourself."
"This user already has access to this item": "Bu kullanıcının zaten bu öğeye erişimi var. ", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "Değiştir", // In English: "Change"
+ "billing.cancel": "İptal et", // In English: "Cancel"
+ "billing.download_invoice": "İndir", // In English: "Download"
+ "billing.payment_method": "Ödeme Yöntemi", // In English: "Payment Method"
+ "billing.payment_method_updated": "Ödeme yöntemi güncellendi!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Ödeme Yöntemini Onayla", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Ödeme Geçmişi", // In English: "Payment History"
+ "billing.refunded": "İade edildi", // In English: "Refunded"
+ "billing.paid": "Ödendi", // In English: "Paid"
+ "billing.ok": "Tamam", // In English: "OK"
+ "billing.resume_subscription": "Aboneliğe Devam Et", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Aboneliğin iptal edildi.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Bu fatura döneminin sonuna kadar aboneliğinizi kullanmaya devam edebilirsiniz.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Ücretsiz", // In English: "Free"
+ "billing.offering.pro": "Profesyonel", // In English: "Professional"
+ "billing.offering.business": "İşletme", // In English: "Business"
+ "billing.cloud_storage": "Bulut Depolama", // In English: "Cloud Storage"
+ "billing.ai_access": "Yapay Zeka Erişimi", // In English: "AI Access"
+ "billing.bandwidth": "Bant Genişliği", // In English: "Bandwidth"
+ "billing.apps_and_games": "Uygulamalar & Oyunlar", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Yükselt: %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Değiştir: %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Ödeme Ayarları", // In English: "Payment Setup"
+ "billing.back": "Geri", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "%strong% seviyesine abone oldunuz.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Abone oldunuz", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Aboneliğinizi iptal etmek istediğinize emin misiniz?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Abonelik Ayarları", // In English: "Subscription Setup"
+ "billing.cancel_it": "İptal Et", // In English: "Cancel It"
+ "billing.keep_it": "Sürdür", // In English: "Keep It"
+ "billing.subscription_resumed": "%strong% aboneliğiniz yeniden başlatıldı!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Şimdi Yükselt", // In English: "Upgrade Now"
+ "billing.upgrade": "Yükselt", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Şu anda ücretsiz plandasınız.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Makbuzu İndir", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Abonelik durumunuzu kontrol ederken bir sorun oluştu.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "E-Postanız henüz onaylanmadı. Onaylamanız için bir kod göndereceğiz.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Aboneliğinizi iptal ettiniz ve bu fatura döneminin sonunda otomatik olarak ücretsiz plana geçeceksiniz. Tekrar abone olmadığınız sürece size yeniden ücret yansıtılmayacak.", // 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": "Bu fatura döneminin sonuna kadar geçerli olan mevcut planınız.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Mevcut planınız", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "İptal Edilen Abonelik (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Yönet", // In English: "Manage"
+ "billing.limited": "Kısıtlı", // In English: "Limited"
+ "billing.expanded": "Genişletilmiş", // In English: "Expanded"
+ "billing.accelerated": "Hızlandırılmış", // In English: "Accelerated"
+ "billing.enjoy_msg": "Diğer avantajların yanı sıra %% Bulut Depolamanın keyfini çıkarın.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
-export default tr;
\ No newline at end of file
+export default tr;
diff --git a/src/gui/src/i18n/translations/translations.js b/src/gui/src/i18n/translations/translations.js
index d9f493889c..6cb0a3eb0c 100644
--- a/src/gui/src/i18n/translations/translations.js
+++ b/src/gui/src/i18n/translations/translations.js
@@ -22,6 +22,7 @@ import bn from './bn.js';
import br from './br.js';
import da from './da.js';
import de from './de.js';
+import emoji from './emoji.js';
import en from './en.js';
import es from './es.js';
import fa from './fa.js';
@@ -29,7 +30,9 @@ import fi from './fi.js';
import fr from './fr.js';
import he from './he.js';
import hi from './hi.js';
+import hu from './hu.js';
import hy from './hy.js';
+import id from './id.js';
import it from './it.js';
import ig from './ig.js';
import ja from './ja.js';
@@ -46,14 +49,11 @@ import sv from './sv.js';
import ta from './ta.js';
import th from './th.js';
import tr from './tr.js';
-import ur from './ur.js';
import ua from './ua.js';
+import ur from './ur.js';
+import vi from "./vi.js";
import zh from './zh.js';
-import hu from './hu.js';
import zhtw from './zhtw.js';
-import vi from "./vi.js";
-import emoji from './emoji.js';
-import id from './id.js';
export default {
ar,
@@ -61,6 +61,7 @@ export default {
br,
da,
de,
+ emoji,
en,
es,
fa,
@@ -68,29 +69,28 @@ export default {
fr,
he,
hi,
+ hu,
hy,
- it,
+ id,
ig,
+ it,
ja,
ko,
ku,
nb,
nl,
nn,
- ro,
- ru,
pl,
pt,
+ ro,
+ ru,
sv,
ta,
th,
tr,
ua,
- zh,
- hu,
- zhtw,
ur,
vi,
- emoji,
- id,
+ zh,
+ zhtw,
};
diff --git a/src/gui/src/i18n/translations/ua.js b/src/gui/src/i18n/translations/ua.js
index e4582e0ef5..eee1183b87 100644
--- a/src/gui/src/i18n/translations/ua.js
+++ b/src/gui/src/i18n/translations/ua.js
@@ -27,34 +27,34 @@ const ua = {
account_password: "Перевірити пароль облікового запису",
access_granted_to: "Доступ надано",
add_existing_account: "Додати існуючий обліковий запис",
- all_fields_required: 'Усі поля обов\'язкові.',
+ all_fields_required: "Усі поля обов\'язкові.",
allow: "Дозволити",
apply: "Застосувати",
- ascending: 'За зростанням',
+ ascending: "За зростанням",
associated_websites: "Асоційовані веб-сайти",
auto_arrange: 'Автоупорядкування',
background: "Фон",
browse: "Переглянути",
- cancel: 'Відміна',
- center: 'Відцентрувати',
- change: 'Змінити',
- change_desktop_background: 'Змінити фон робочого столу…',
+ cancel: "Відміна",
+ center: "Відцентрувати",
+ change: "Змінити",
+ change_desktop_background: "Змінити фон робочого столу…",
change_email: "Змінити Email",
change_language: "Змінити Мову",
change_password: "Змінити Пароль",
change_ui_colors: "Змінити Тему Оформлення",
change_username: "Змінити Ім\'я Користувача",
- clock_visibility: 'Видимість годинника',
- close: 'Закрити',
+ clock_visibility: "Видимість годинника",
+ close: "Закрити",
close_all_windows: "Закрити всі Вікна",
close_all_windows_confirm: "Ви впевнені, що хочете закрити всі вікна?",
- close_all_windows_and_log_out: 'Закрити Вікна і Вийти',
+ close_all_windows_and_log_out: "Закрити Вікна і Вийти",
change_always_open_with: "Бажаєте завжди відкривати файли цього типу в",
- color: 'Колір',
+ color: "Колір",
confirm: "Підтвердити",
confirm_2fa_setup: "Я додав код у свій додаток для аутентифікації",
confirm_2fa_recovery: "Я зберіг свої коди для відновлення в безпечному місці",
- confirm_account_for_free_referral_storage_c2a: 'Створіть обліковий запис і підтвердіть свою електронну адресу, щоб отримати 1 Гб безкоштовного дискового простору. Ваш друг також отримає 1 Гб безкоштовного дискового простору.',
+ confirm_account_for_free_referral_storage_c2a: "Створіть обліковий запис і підтвердіть свою електронну адресу, щоб отримати 1 Гб безкоштовного дискового простору. Ваш друг також отримає 1 Гб безкоштовного дискового простору.",
confirm_code_generic_incorrect: "Код невірний",
confirm_code_generic_too_many_requests: "Забагато запитів. Будь ласка, зачекайте кілька хвилин",
confirm_code_generic_submit: "Прийняти код",
@@ -63,9 +63,9 @@ const ua = {
confirm_code_2fa_instruction: "Введіть шестизначний код з вашого додатка для аутентифікації.",
confirm_code_2fa_submit_btn: "Прийняти",
confirm_code_2fa_title: "Введіть код для двофакторної аутентифікації",
- confirm_delete_multiple_items: 'Ви впевнені, що хочете назавжди видалити ці елементи?',
- confirm_delete_single_item: 'Ви впевнені, що хочете назавжди видалити цей елемент?',
- confirm_open_apps_log_out: 'У вас є відкриті додатки. Ви впевнені, що хочете вийти з системи?',
+ confirm_delete_multiple_items: "Ви впевнені, що хочете назавжди видалити ці елементи?",
+ confirm_delete_single_item: "Ви впевнені, що хочете назавжди видалити цей елемент?",
+ confirm_open_apps_log_out: "У вас є відкриті додатки. Ви впевнені, що хочете вийти з системи?",
confirm_new_password: "Підтвердьте новий пароль",
confirm_delete_user: "Ви впевнені, що хочете видалити свій обліковий запис? Усі ваші файли та дані будуть видалені назавжди. Цю дію неможливо скасувати.",
confirm_delete_user_title: "Видалити обліковий запис?",
@@ -73,33 +73,33 @@ const ua = {
confirm_your_email_address: "Підтвердити електронну адресу",
contact_us: "Зв'яжіться з нами",
contact_us_verification_required: "Вам необхідно мати підтверджену електронну адресу для використання цієї функції",
- contain: 'Зміст',
+ contain: "Зміст",
continue: "Продовжити",
- copy: 'Копіювати',
+ copy: "Копіювати",
copy_link: "Копіювати Посилання",
copying: "Копіюється",
copying_file: "Копіюється %%",
- cover: 'Обкладинка',
+ cover: "Обкладинка",
create_account: "Створити Обліковий Запис",
create_free_account: "Створити Безкоштовний Обліковий Запис",
create_shortcut: "Створити Ярлик",
credits: "Титри",
current_password: "Поточний Пароль",
- cut: 'Вирізати',
+ cut: "Вирізати",
clock: "Годинник",
- clock_visible_hide: 'Приховати - Завжди приховано',
- clock_visible_show: 'Показати - Завжди на виду',
- clock_visible_auto: 'Авто - За Замовчуванням, видно тільки у повноекранному режимі',
+ clock_visible_hide: "Приховати - Завжди приховано",
+ clock_visible_show: "Показати - Завжди на виду",
+ clock_visible_auto: "Авто - За Замовчуванням, видно тільки у повноекранному режимі",
close_all: "Закрити все",
created: "Створено",
- date_modified: 'Дата зміни',
- default: 'За замовчуванням',
- delete: 'Видалити',
+ date_modified: "Дата зміни",
+ default: "За замовчуванням",
+ delete: "Видалити",
delete_account: "Видалити Обліковий Запис",
delete_permanently: "Видалити Назавжди",
deleting_file: "Видалення %%",
- deploy_as_app: 'Розгорнути як додаток',
- descending: 'За спаданням',
+ deploy_as_app: "Розгорнути як додаток",
+ descending: "За спаданням",
desktop: "Робочий стіл",
desktop_background_fit: "Вмістити",
developers: "Розробники",
@@ -110,17 +110,17 @@ const ua = {
disassociate_dir: "Від'єднати Директорію",
documents: "Документи",
dont_allow: "Не дозволяти",
- download: 'Завантажити',
- download_file: 'Завантажити Файл',
+ download: "Завантажити",
+ download_file: "Завантажити Файл",
downloading: "Завантажується",
email: "Email",
email_change_confirmation_sent: "На вашу нову електронну адресу було надіслано листа з підтвердженням. Будь ласка, перевірте свою поштову скриньку і дотримуйтесь інструкцій, щоб завершити процес.",
- email_invalid: 'Електронна адреса недійсна.',
+ email_invalid: "Електронна адреса недійсна.",
email_or_username: "Email або Ім'я Користувача",
- email_required: 'Email обов\'язковий.',
- empty_trash: 'Очистити Кошик',
+ email_required: "Email обов\'язковий.",
+ empty_trash: "Очистити Кошик",
empty_trash_confirmation: `Ви впевнені, що хочете назавжди видалити елементи з Кошика?`,
- emptying_trash: 'Очищення Кошика…',
+ emptying_trash: "Очищення Кошика…",
enable_2fa: "Увімкнути двофакторну аутентифікацію",
end_hard: "Закрити жорстко",
end_process_force_confirm: "Ви впевнені, що хочете примусово завершити цей процес?",
@@ -136,32 +136,32 @@ const ua = {
feedback_sent_confirmation: "Дякуємо, що зв'язалися з нами. Якщо у вас є електронна пошта, пов'язана з вашим обліковим записом, ми відповімо вам якомога швидше.",
fit: "Вмістити",
folder: "Папка",
- force_quit: 'Примусово Закрити',
+ force_quit: "Примусово Закрити",
forgot_pass_c2a: "Забули пароль?",
from: "Від",
general: "Загальний",
get_a_copy_of_on_puter: `Отримайте копію '%%' на Puter.com!`,
- get_copy_link: 'Отримати Посилання для Копіювання',
+ get_copy_link: "Отримати Посилання для Копіювання",
hide_all_windows: "Приховати всі вікна",
home: "Додому",
- html_document: 'HTML документ',
- hue: 'Колірна Гама',
- image: 'Зображення',
+ html_document: "HTML документ",
+ hue: "Колірна Гама",
+ image: "Зображення",
incorrect_password: "Невірний пароль",
invite_link: "Невірне посилання",
- item: 'Елемент',
- items_in_trash_cannot_be_renamed: `Цей елемент неможливо переіменувати тому що він знаходиться у корзині. Спочатку
- перетащіть його з корзини`,
- jpeg_image: 'JPEG зображення',
- keep_in_taskbar: 'Зберегти на Панелі Задач',
+ item: "Елемент",
+ items_in_trash_cannot_be_renamed: `Цей елемент неможливо переіменувати тому що він знаходиться у кошику. Спочатку
+ перемістіть його з корзини`,
+ jpeg_image: "JPEG зображення",
+ keep_in_taskbar: "Зберегти на Панелі Задач",
language: "Мова",
license: "Ліцензія",
- lightness: 'Легкість',
+ lightness: "Яскравість",
link_copied: "Посилання скопійоване",
- loading: 'Завантажується',
+ loading: "Завантажується",
log_in: "Ввійти",
- log_into_another_account_anyway: 'Все одно ввійти в інший аккаунт',
- log_out: 'Вийти',
+ log_into_another_account_anyway: "Все одно увійти в інший аккаунт",
+ log_out: "Вийти",
looks_good: "Гарно виглядає!",
manage_sessions: "Управління Сеансами",
menubar_style: "Стиль Меню",
@@ -169,41 +169,41 @@ const ua = {
menubar_style_system: "Системи",
menubar_style_window: "Вікна",
modified: "Змінено",
- move: 'Перемістити',
+ move: "Перемістити",
moving_file: "Переміщується %%",
my_websites: "Мої Сайти",
- name: 'Ім\'я',
- name_cannot_be_empty: 'Ім\'я не може бути порожнім.',
+ name: "Ім\'я",
+ name_cannot_be_empty: "Ім\'я не може бути порожнім.",
name_cannot_contain_double_period: "Ім'я не может бути '..' символом.",
name_cannot_contain_period: "Ім'я не може бути '.' символом.",
name_cannot_contain_slash: "Ім'я не може містити '/' символ.",
name_must_be_string: "Ім\'я може містити тільки текстові символи",
name_too_long: `Ім\'я не може бути більш ніж %% символів уздовж.`,
- new: 'Новий',
- new_email: 'Новий Email',
- new_folder: 'Нова папка',
+ new: "Новий",
+ new_email: "Новий Email",
+ new_folder: "Нова папка",
new_password: "Новий Пароль",
new_username: "Нове Ім'я Користувача",
- no: 'Ні',
- no_dir_associated_with_site: 'Немає директорії, пов\'язанної з цією адресою.',
+ no: "Ні",
+ no_dir_associated_with_site: "Немає директорії, пов\'язанної з цією адресою.",
no_websites_published: "Ви ще не опублікували жодного сайту.",
- ok: 'OK',
+ ok: "Так",
open: "Відчинити",
open_in_new_tab: "Відчинити у Новій Вкладці",
open_in_new_window: "Відчинити у Новому Вікні",
open_with: "Відчинити за допомогою",
original_name: "Оригінальне Ім'я",
- original_path: "Оригинальный путь",
+ original_path: "Оригінальний шлях",
oss_code_and_content: "Програмне забезпечення та контент з відкритим кодом",
password: "Пароль",
password_changed: "Пароль змінено.",
password_recovery_rate_limit: "Ви досягли ліміту; будь ласка, зачекайте кілька хвилин. Щоб уникнути цього в майбутньому, не перезавантажуйте сторінку занадто багато разів.",
password_recovery_token_invalid: "Цей токен відновлення пароля більше не дійсний.",
password_recovery_unknown_error: "Сталася невідома помилка. Будь ласка, спробуйте пізніше.",
- password_required: 'Потрібен Пароль.',
+ password_required: "Потрібен Пароль.",
password_strength_error: "Пароль має містити не менше 8 символів і включати хоча б одну велику літеру, одну малу літеру, одну цифру та один спеціальний символ.",
- passwords_do_not_match: 'Поля Новий Пароль і Підтвердіть Новий Пароль не співпадають.',
- paste: 'Вставити',
+ passwords_do_not_match: "Поля Новий Пароль і Підтвердіть Новий Пароль не співпадають.",
+ paste: "Вставити",
paste_into_folder: "Вставити в Папку",
path: "Шлях",
personalization: "Персоналізація",
@@ -214,19 +214,19 @@ const ua = {
powered_by_puter_js: "Створено на {{link=docs}}Puter.js{{/link}}",
preparing: "Підготовка...",
preparing_for_upload: "Підготовка до завантаження...",
- print: 'Друкувати',
+ print: "Друкувати",
privacy: "Конфіденційність",
- proceed_to_login: 'Перейти до Входу',
+ proceed_to_login: "Перейти до Входу",
proceed_with_account_deletion: "Продовжити Видалення Облікового Запису",
process_status_initializing: "Ініціалізація",
process_status_running: "Виконується",
- process_type_app: 'Дод.',
- process_type_init: 'Ініц.',
- process_type_ui: 'Інтерфейс користувача',
+ process_type_app: "Дод.",
+ process_type_init: "Ініц.",
+ process_type_ui: "Інтерфейс користувача",
properties: "Властивості",
public: "Загальний",
publish: "Опублікувати",
- publish_as_website: 'Опублікувати як сайт',
+ publish_as_website: "Опублікувати як сайт",
puter_description: "Puter — це персональна хмара, яка забезпечує конфіденційність, дозволяючи зберігати всі ваші файли, додатки та ігри в одному безпечному місці, доступному з будь-якого місця в будь-який час.",
reading: "Читання %strong%",
reading_file: "Читання файлу",
@@ -235,33 +235,33 @@ const ua = {
recover_password: "Відновити Пароль",
refer_friends_c2a: "Отримайте 1 ГБ за кожного друга, який створить і підтвердить обліковий запис на Puter. Ваш друг також отримає 1 ГБ!",
refer_friends_social_media_c2a: "Отримайте 1 ГБ безкоштовного сховища на Puter.com!",
- refresh: 'Оновити',
+ refresh: "Оновити",
release_address_confirmation: "Ви впевнені, що хочете звільнити цю адресу?",
- remove_from_taskbar: 'Видалити з Панелі Завдань',
- rename: 'Перейменувати',
- repeat: 'Повторити',
- replace: 'Замінити',
- replace_all: 'Замінити Все',
+ remove_from_taskbar: "Видалити з Панелі Завдань",
+ rename: "Перейменувати",
+ repeat: "Повторити",
+ replace: "Замінити",
+ replace_all: "Замінити Все",
resend_confirmation_code: "Повторно надіслати Код Підтвердження",
reset_colors: "Скинути Кольори",
restart_puter_confirm: "Ви впевнені, що хочете перезапустити Puter?",
restore: "Відновити",
save: "Зберегти",
- saturation: 'Насиченість',
- save_account: 'Зберегти Обліковий запис',
+ saturation: "Насиченість",
+ save_account: "Зберегти Обліковий запис",
save_account_to_get_copy_link: "Будь ласка, створіть обліковий запис, щоб продовжити.",
- save_account_to_publish: 'Будь ласка, створіть обліковий запис, щоб продовжити.',
- save_session: 'Зберегти сеанс',
- save_session_c2a: 'Створіть обліковий запис, щоб зберегти поточний сеанс і не втратити дані.',
- scan_qr_c2a: 'Скануйте код нижче, щоб увійти в цей сеанс з інших пристроїв',
+ save_account_to_publish: "Будь ласка, створіть обліковий запис, щоб продовжити.",
+ save_session: "Зберегти сеанс",
+ save_session_c2a: "Створіть обліковий запис, щоб зберегти поточний сеанс і не втратити дані.",
+ scan_qr_c2a: "Скануйте код нижче, щоб увійти в цей сеанс з інших пристроїв",
scan_qr_2fa: "Скануйте код за допомогою вашого додатку для аутентифікації",
- scan_qr_generic: 'Скануйте цей QR-код за допомогою телефону або іншого пристрою',
+ scan_qr_generic: "Скануйте цей QR-код за допомогою телефону або іншого пристрою",
search: "Пошук",
- seconds: 'секунди',
+ seconds: "секунди",
security: "Безпека",
select: "Вибрати",
- selected: 'вибрано',
- select_color: 'Вибрати колір…',
+ selected: "вибрано",
+ select_color: "Вибрати колір…",
sessions: "Сеанси",
send: "Надіслати",
send_password_recovery_email: "Надіслати електронний лист для відновлення пароля",
@@ -277,70 +277,70 @@ const ua = {
sign_in_with_puter: "Увійти з Puter",
sign_up: "Зареєструватися",
signing_in: "Вхід у систему…",
- size: 'Розмір',
- skip: 'Пропустити',
- sort_by: 'Відсортувати за',
- start: 'Почати',
+ size: "Розмір",
+ skip: "Пропустити",
+ sort_by: "Відсортувати за",
+ start: "Почати",
status: "Статус",
storage_usage: "Використання сховища",
- storage_puter_used: 'використано Puter',
- taking_longer_than_usual: 'Це займає трохи більше часу, ніж зазвичай. будь ласка, зачекайте...',
+ storage_puter_used: "використано Puter",
+ taking_longer_than_usual: "Це займає трохи більше часу, ніж зазвичай. будь ласка, зачекайте...",
task_manager: "Диспетчер Завдань",
taskmgr_header_name: "Ім'я",
taskmgr_header_status: "Статус",
taskmgr_header_type: "Тип",
terms: "Умови",
- text_document: 'Текстовий документ',
+ text_document: "Текстовий документ",
tos_fineprint: "Натискаючи 'Створити безкоштовний обліковий запис', ви погоджуєтеся з {{link=terms}}Умовами Використання{{/link}} та {{link=privacy}}Політикою Конфіденційності{{/link}} Puter.",
transparency: "Прозорість",
- trash: 'Кошик',
+ trash: "Кошик",
two_factor: "Двофакторна аутентифікація",
two_factor_disabled: "Двофакторна аутентифікація вимкнена",
two_factor_enabled: "Двофакторна аутентифікація увімкнена",
- type: 'Тип',
+ type: "Тип",
type_confirm_to_delete_account: "Введіть 'Підтвердити', щоб видалити обліковий запис.",
ui_colors: "Кольори UI",
ui_manage_sessions: "Менеджер Сеансів",
ui_revoke: "Відкликати",
- undo: 'Скасувати',
- unlimited: 'Необмежено',
+ undo: "Скасувати",
+ unlimited: "Необмежено",
unzip: "Розпакувати",
unzipping: "Розпакування %strong%",
- upload: 'Завантажити',
- upload_here: 'Завантажити тут',
- usage: 'Використання',
+ upload: "Завантажити",
+ upload_here: "Завантажити тут",
+ usage: "Використання",
username: "Ім'я Користувача",
username_changed: "Ім'я Користувача успішно оновлено.",
username_required: "Потрібно ім'я Користувача.",
versions: "Версії",
videos: "Відео",
- visibility: 'Видимість',
- writing: 'Написання %strong%',
- yes: 'Так',
- yes_release_it: 'Так, звільнити.',
+ visibility: "Видимість",
+ writing: "Написання %strong%",
+ yes: "Так",
+ yes_release_it: "Так, звільнити.",
you_have_been_referred_to_puter_by_a_friend: "Вас запросив друг у Puter!",
zip: "Архівувати",
zipping: "Архівування %strong%",
zipping_file: "Архівування %strong%",
sequencing: "Порядок %strong%",
- setup2fa_1_step_heading: 'Відкрийте ваш додаток для аутентифікації',
+ setup2fa_1_step_heading: "Відкрийте ваш додаток для аутентифікації",
setup2fa_1_instructions: `Ви можете використовувати будь-який додаток, який підтримує Одноразовий Пароль на основі часу. Їх багато, але якщо ви не впевнені у виборі Authy є чудовим варіантом для Android і iOS.`,
- setup2fa_2_step_heading: 'Скануйте QR код',
- setup2fa_3_step_heading: 'Введіть шестизначний код',
- setup2fa_4_step_heading: 'Скопіюйте ваші коди відновлення',
+ setup2fa_2_step_heading: "Скануйте QR код",
+ setup2fa_3_step_heading: "Введіть шестизначний код",
+ setup2fa_4_step_heading: "Скопіюйте ваші коди відновлення",
setup2fa_4_instructions: "Ці коди відновлення є єдиним способом входу у ваш обліковий запис, якщо ви втратите телефон або не зможете використовувати додаток для аутентифікації. Упевніться, що ви їх зберегли.",
- setup2fa_5_step_heading: 'Підтвердьте налаштування 2FA',
- setup2fa_5_confirmation_1: 'Я зберіг свої коди в надійному місці',
- setup2fa_5_confirmation_2: 'Я готовий використовувати 2FA',
- setup2fa_5_button: 'Увімкнути 2FA',
+ setup2fa_5_step_heading: "Підтвердьте налаштування 2FA",
+ setup2fa_5_confirmation_1: "Я зберіг свої коди в надійному місці",
+ setup2fa_5_confirmation_2: "Я готовий використовувати 2FA",
+ setup2fa_5_button: "Увімкнути 2FA",
something_went_wrong: "Щось пішло не так.",
- login2fa_otp_title: 'Введіть код 2FA',
- login2fa_otp_instructions: 'Введіть шестизначний код з вашого додатку для аутентифікації',
- login2fa_recovery_title: 'Введіть код відновлення',
- login2fa_recovery_instructions: 'Введіть один з кодів відновлення для доступу до облікового запису',
- login2fa_use_recovery_code: 'Використовуйте код відновлення',
- login2fa_recovery_back: 'Назад',
- login2fa_recovery_placeholder: 'XXXXXXXX',
+ login2fa_otp_title: "Введіть код 2FA",
+ login2fa_otp_instructions: "Введіть шестизначний код з вашого додатку для аутентифікації",
+ login2fa_recovery_title: "Введіть код відновлення",
+ login2fa_recovery_instructions: "Введіть один з кодів відновлення для доступу до облікового запису",
+ login2fa_use_recovery_code: "Використовуйте код відновлення",
+ login2fa_recovery_back: "Назад",
+ login2fa_recovery_placeholder: "XXXXXXXX",
Editor: "Редактор",
Viewer: "Переглядач",
"People with access": "Люди з доступом",
@@ -348,6 +348,53 @@ const ua = {
Owner: "Власник",
"You can't share with yourself.": "Ви не можете поділитися з собою.",
"This user already has access to this item": "Цей користувач уже має доступ до цього елементу",
+ "billing.change_payment_method": "Змінити",
+ "billing.cancel": "Скасувати",
+ "billing.download_invoice": "Завантажити",
+ "billing.payment_method": "Спосіб оплати",
+ "billing.payment_method_updated": "Спосіб оплати оновлено!",
+ "billing.confirm_payment_method": "Підтвердити спосіб оплати",
+ "billing.payment_history": "Історія платежів",
+ "billing.refunded": "Платіж повернено",
+ "billing.paid": "Cплачено",
+ "billing.ok": "Ок",
+ "billing.resume_subscription": "Поновити підписку",
+ "billing.subscription_cancelled": "Вашу підписку було скасовано.",
+ "billing.subscription_cancelled_description": "Ви матимете доступ до своєї підписки до кінця поточного розрахункового періоду.",
+ "billing.offering.free": "Free", // In English: "Free"
+ "billing.offering.pro": "Professional", // In English: "Professional"
+ "billing.offering.business": "Business", // In English: "Business"
+ "billing.cloud_storage": "Хмарне сховище",
+ "billing.ai_access": "Доступ до ШІ",
+ "billing.bandwidth": "Пропускна здатність",
+ "billing.apps_and_games": "Додатки та ігри",
+ "billing.upgrade_to_pro": "Оновити до %strong%",
+ "billing.switch_to": "Перейти до %strong%",
+ "billing.payment_setup": "Налаштування платежів",
+ "billing.back": "Назад",
+ "billing.you_are_now_subscribed_to": "Тепер рівень Вашої підписки — %strong%",
+ "billing.you_are_now_subscribed_to_without_tier": "Тепер Ви маєте підписку",
+ "billing.subscription_cancellation_confirmation": "Ви впевнені, що хочете скасувати підписку?",
+ "billing.subscription_setup": "Налаштування підписки",
+ "billing.cancel_it": "Скасувати",
+ "billing.keep_it": "Залишити",
+ "billing.subscription_resumed": "Вашу підписку %strong% було поновлено!",
+ "billing.upgrade_now": "Оновити зараз",
+ "billing.upgrade": "Оновити",
+ "billing.currently_on_free_plan": "Наразі Ви використовуєте безкоштовний тариф.",
+ "billing.download_receipt": "Завантажити квитанцію",
+ "billing.subscription_check_error": "Під час перевірки статусу підписки виникла проблема",
+ "billing.email_confirmation_needed": "Ваша електронна пошта не була підтверджена. Ми надішлемо Вам код для підтвердження.",
+ "billing.sub_cancelled_but_valid_until": "Ви скасували підписку, і вона автоматично перейде на безкоштовний рівень в кінці розрахункового періоду. Плата за підписку не стягуватиметься, якщо ви не оформите її повторно.",
+ "billing.current_plan_until_end_of_period": "Ваш тарифний план до кінця поточного розрахункового періоду",
+ "billing.current_plan": "Поточний тарифний план",
+ "billing.cancelled_subscription_tier": "Скасована підписка (%%)",
+ "billing.manage": "Керування",
+ "billing.limited": "Обмежений",
+ "billing.expanded": "Розширений",
+ "billing.accelerated": "Прискорений",
+ "billing.enjoy_msg": "Насолоджуйтесь %% хмарного сховища та іншими перевагами.",
+
}
}
diff --git a/src/gui/src/i18n/translations/ur.js b/src/gui/src/i18n/translations/ur.js
index fa33e5029c..82b13b28c9 100644
--- a/src/gui/src/i18n/translations/ur.js
+++ b/src/gui/src/i18n/translations/ur.js
@@ -391,6 +391,54 @@ const ur = {
"Owner": "مالک", // In English: "Owner"
"You can't share with yourself.": "آپ خود کے ساتھ شیئر نہیں کر سکتے۔", // In English: "You can't share with yourself."
"This user already has access to this item": "اس صارف کو پہلے ہی اس آئٹم تک رسائی حاصل ہے۔", // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "ادائیگی کے طریقہ کو تبدیل کریں", // In English: "Change"
+ "billing.cancel": "منسوخ کریں", // In English: "Cancel"
+ "billing.download_invoice": "انوائس ڈاؤن لوڈ کریں", // In English: "Download"
+ "billing.payment_method": "ادائیگی کا طریقہ", // In English: "Payment Method"
+ "billing.payment_method_updated": "ادائیگی کا طریقہ اپ ڈیٹ کر دیا گیا ہے!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "ادائیگی کے طریقہ کی تصدیق کریں", // In English: "Confirm Payment Method"
+ "billing.payment_history": "ادائیگی کی تاریخ", // In English: "Payment History"
+ "billing.refunded": "رقم واپس کر دی گئی", // In English: "Refunded"
+ "billing.paid": "ادائیگی ہو چکی ہے", // In English: "Paid"
+ "billing.ok": "ٹھیک ہے", // In English: "OK"
+ "billing.resume_subscription": "سبسکرپشن بحال کریں", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "آپ کی سبسکرپشن منسوخ کر دی گئی ہے۔", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "آپ کو اس بلنگ مدت کے اختتام تک اپنی سبسکرپشن تک رسائی حاصل رہے گی۔", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "مفت", // In English: "Free"
+ "billing.offering.pro": "پروفیشنل", // In English: "Professional"
+ "billing.offering.business": "کاروبار", // In English: "Business"
+ "billing.cloud_storage": "کلاؤڈ اسٹوریج", // In English: "Cloud Storage"
+ "billing.ai_access": "اے آئی تک رسائی", // In English: "AI Access"
+ "billing.bandwidth": "بینڈوتھ", // In English: "Bandwidth"
+ "billing.apps_and_games": "ایپس اور گیمز", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "پروفیشنل میں اپ گریڈ کریں", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "%strong% پر منتقل ہوں", // In English: "Switch to %strong%"
+ "billing.payment_setup": "ادائیگی کی ترتیب", // In English: "Payment Setup"
+ "billing.back": "پیچھے", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "آپ اب %strong% سطح کی سبسکرپشن کے حامل ہیں۔", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "آپ اب سبسکرائب ہیں۔", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "کیا آپ واقعی اپنی سبسکرپشن منسوخ کرنا چاہتے ہیں؟", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "سبسکرپشن سیٹ اپ", // In English: "Subscription Setup"
+ "billing.cancel_it": "اسے منسوخ کریں", // In English: "Cancel It"
+ "billing.keep_it": "اسے رکھیں", // In English: "Keep It"
+ "billing.subscription_resumed": "آپ کی %strong% سبسکرپشن دوبارہ شروع کر دی گئی ہے!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "اب اپ گریڈ کریں", // In English: "Upgrade Now"
+ "billing.upgrade": "اپ گریڈ کریں", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "آپ اس وقت مفت پلان پر ہیں۔", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "رسیٹ ڈاؤن لوڈ کریں", // In English: "Download Receipt"
+ "billing.subscription_check_error": "آپ کی سبسکرپشن کی حیثیت چیک کرتے وقت مسئلہ پیش آیا۔", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "آپ کا ای میل تصدیق شدہ نہیں ہے۔ ہم اسے تصدیق کرنے کے لیے آپ کو ایک کوڈ بھیجیں گے۔", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "آپ نے اپنی سبسکرپشن منسوخ کر دی ہے اور یہ بلنگ پیریڈ کے آخر تک مفت پلان پر منتقل ہو جائے گی۔ آپ کو دوبارہ سبسکرائب کرنے تک دوبارہ چارج نہیں کیا جائے گا۔", // 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": "آپ کا موجودہ پلان اس بلنگ پیریڈ کے آخر تک برقرار رہے گا۔", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "موجودہ پلان", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "منسوخ شدہ سبسکرپشن (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "انتظام کریں", // In English: "Manage"
+ "billing.limited": "محدود", // In English: "Limited"
+ "billing.expanded": "وسیع", // In English: "Expanded"
+ "billing.accelerated": "تیز رفتار", // In English: "Accelerated"
+ "billing.enjoy_msg": "کلاؤڈ اسٹوریج کے %% حصے کے ساتھ دیگر فوائد کا لطف اٹھائیں۔", // In English: "Enjoy %% of Cloud Storage plus other benefits."
+
},
};
diff --git a/src/gui/src/i18n/translations/vi.js b/src/gui/src/i18n/translations/vi.js
index bf132e03c1..0963d2509a 100644
--- a/src/gui/src/i18n/translations/vi.js
+++ b/src/gui/src/i18n/translations/vi.js
@@ -362,6 +362,53 @@ const vi = {
"Owner": 'Người sở hữu', // In English: "Owner"
"You can't share with yourself.": 'Bạn không thể tự chia sẻ với chính mình', // In English: "You can't share with yourself."
"This user already has access to this item": 'Người dùng này đã có sẵn quyền truy cập cho mục này', // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "Thay đổi", // In English: "Change"
+ "billing.cancel": "Hủy", // In English: "Cancel"
+ "billing.download_invoice": "Tải xuống", // In English: "Download"
+ "billing.payment_method": "Phương thức thanh toán", // In English: "Payment Method"
+ "billing.payment_method_updated": "Phương thức thanh toán đã được cập nhật thành công!", // In English: "Payment method updated!"
+ "billing.confirm_payment_method": "Xác nhận phương thức thanh toán", // In English: "Confirm Payment Method"
+ "billing.payment_history": "Lịch sử thanh toán", // In English: "Payment History"
+ "billing.refunded": "Đã hoàn tiền", // In English: "Refunded"
+ "billing.paid": "Đã thanh toán", // In English: "Paid"
+ "billing.ok": "OK", // In English: "OK"
+ "billing.resume_subscription": "Tiếp tục đăng ký", // In English: "Resume Subscription"
+ "billing.subscription_cancelled": "Đăng ký của bạn đã bị hủy.", // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": "Bạn vẫn có thể tiếp tục sử dụng dịch vụ của mình cho đến cuối kỳ thanh toán này.", // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": "Miễn phí", // In English: "Free"
+ "billing.offering.pro": "Chuyên nghiệp", // In English: "Professional"
+ "billing.offering.business": "Doanh nghiệp", // In English: "Business"
+ "billing.cloud_storage": "Lưu trữ đám mây", // In English: "Cloud Storage"
+ "billing.ai_access": "Truy cập AI", // In English: "AI Access"
+ "billing.bandwidth": "Băng thông", // In English: "Bandwidth"
+ "billing.apps_and_games": "Ứng dụng & Trò chơi", // In English: "Apps & Games"
+ "billing.upgrade_to_pro": "Nâng cấp lên %strong%", // In English: "Upgrade to %strong%"
+ "billing.switch_to": "Chuyển sang gói %strong%", // In English: "Switch to %strong%"
+ "billing.payment_setup": "Thiết lập thanh toán", // In English: "Payment Setup"
+ "billing.back": "Quay lại", // In English: "Back"
+ "billing.you_are_now_subscribed_to": "Bạn hiện đang đăng ký gói %strong%.", // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": "Bạn đã đăng ký", // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": "Bạn có chắc chắn muốn hủy đăng ký không?", // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": "Thiết lập đăng ký", // In English: "Subscription Setup"
+ "billing.cancel_it": "Hủy bỏ", // In English: "Cancel It"
+ "billing.keep_it": "Giữ lại", // In English: "Keep It"
+ "billing.subscription_resumed": "Đăng ký %strong% của bạn đã được tiếp tục!", // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": "Nâng cấp ngay", // In English: "Upgrade Now"
+ "billing.upgrade": "Nâng cấp", // In English: "Upgrade"
+ "billing.currently_on_free_plan": "Bạn hiện đang sử dụng gói miễn phí.", // In English: "You are currently on the free plan."
+ "billing.download_receipt": "Tải biên lai", // In English: "Download Receipt"
+ "billing.subscription_check_error": "Đã xảy ra sự cố khi kiểm tra trạng thái đăng ký của bạn.", // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": "Email của bạn chưa được xác nhận. Chúng tôi sẽ gửi mã xác nhận ngay bây giờ.", // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": "Bạn đã hủy đăng ký và được tự động chuyển sang gói miễn phí vào cuối kỳ thanh toán. Bạn sẽ không bị tính phí nữa đến khi đăng ký lại.", // 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": "Gói cước hiện tại của bạn đến cuối kỳ thanh toán này.", // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": "Gói hiện tại", // In English: "Current plan"
+ "billing.cancelled_subscription_tier": "Đăng ký đã hủy (%%)", // In English: "Cancelled Subscription (%%)"
+ "billing.manage": "Quản lý", // In English: "Manage"
+ "billing.limited": "Giới hạn", // In English: "Limited"
+ "billing.expanded": "Mở rộng", // In English: "Expanded"
+ "billing.accelerated": "Tăng tốc", // In English: "Accelerated"
+ "billing.enjoy_msg": "Tận hưởng %% Lưu trữ đám mây và các tiện ích khác.", // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/zh.js b/src/gui/src/i18n/translations/zh.js
index 025fb12908..3d8ddc1d82 100644
--- a/src/gui/src/i18n/translations/zh.js
+++ b/src/gui/src/i18n/translations/zh.js
@@ -363,7 +363,52 @@ const zh = {
"You can't share with yourself.": '不能分享给你自己', // In English: "You can't share with yourself."
"This user already has access to this item": '该用户已经拥有访问此项目的权限了', // In English: "This user already has access to this item"
-
+ "billing.change_payment_method": '更改', // In English: "Change"
+ "billing.cancel": '取消', // In English: "Cancel"
+ "billing.download_invoice": '下载', // In English: "Download"
+ "billing.payment_method": '付款方式', // In English: "Payment Method"
+ "billing.payment_method_updated": '已更新付款方式!', // In English: "Payment method updated!"
+ "billing.confirm_payment_method": '确认付款方式', // In English: "Confirm Payment Method"
+ "billing.payment_history": '付款历史', // In English: "Payment History"
+ "billing.refunded": '已退款', // In English: "Refunded"
+ "billing.paid": '已付款', // In English: "Paid"
+ "billing.ok": '确认', // In English: "OK"
+ "billing.resume_subscription": '恢复订阅', // In English: "Resume Subscription"
+ "billing.subscription_cancelled": '您的订阅已被取消', // In English: "Your subscription has been canceled."
+ "billing.subscription_cancelled_description": '在此账单期结束前,您仍可使用您的订阅服务。', // In English: "You will still have access to your subscription until the end of this billing period."
+ "billing.offering.free": '免费', // In English: "Free"
+ "billing.offering.pro": '专业', // In English: "Professional"
+ "billing.offering.business": '商业', // In English: "Business"
+ "billing.cloud_storage": '云存储', // In English: "Cloud Storage"
+ "billing.ai_access": '人工智能访问', // In English: "AI Access"
+ "billing.bandwidth": '频宽', // In English: "Bandwidth"
+ "billing.apps_and_games": '应用和游戏', // In English: "Apps & Games"
+ "billing.upgrade_to_pro": '升级至%strong%', // In English: "Upgrade to %strong%"
+ "billing.switch_to": '转至%strong%', // In English: "Switch to %strong%"
+ "billing.payment_setup": '付款设置', // In English: "Payment Setup"
+ "billing.back": '返回', // In English: "Back"
+ "billing.you_are_now_subscribed_to": '您现在已订阅了%strong%级别。', // In English: "You are now subscribed to %strong% tier."
+ "billing.you_are_now_subscribed_to_without_tier": '您现在已订阅', // In English: "You are now subscribed"
+ "billing.subscription_cancellation_confirmation": '您确定要取消订阅吗?', // In English: "Are you sure you want to cancel your subscription?"
+ "billing.subscription_setup": '订阅设置', // In English: "Subscription Setup"
+ "billing.cancel_it": '取消', // In English: "Cancel It"
+ "billing.keep_it": '保留', // In English: "Keep It"
+ "billing.subscription_resumed": '您的%strong%级别的订阅已被恢复!', // In English: "Your %strong% subscription has been resumed!"
+ "billing.upgrade_now": '现在升级', // In English: "Upgrade Now"
+ "billing.upgrade": '升级', // In English: "Upgrade"
+ "billing.currently_on_free_plan": '您目前使用的是免费计划。', // In English: "You are currently on the free plan."
+ "billing.download_receipt": '下载收据', // In English: "Download Receipt"
+ "billing.subscription_check_error": '检查您的订阅状态时遇到错误。', // In English: "A problem occurred while checking your subscription status."
+ "billing.email_confirmation_needed": '您的电邮还没被确认。我们将向您发送一个确认代码。', // In English: "Your email has not been confirmed. We'll send you a code to confirm it now."
+ "billing.sub_cancelled_but_valid_until": '您已取消订阅,在结算期后将会自动转为免费级别。除非您重新订阅,否则不会再向您收取费用。', // 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": '您当前的计划直至本账单期结束。', // In English: "Your current plan until the end of this billing period."
+ "billing.current_plan": '当前计划', // In English: "Current plan"
+ "billing.cancelled_subscription_tier": '已取消的订阅(%%)', // In English: "Cancelled Subscription (%%)"
+ "billing.manage": '管理', // In English: "Manage"
+ "billing.limited": '限制', // In English: "Limited"
+ "billing.expanded": '扩展', // In English: "Expanded"
+ "billing.accelerated": '加快', // In English: "Accelerated"
+ "billing.enjoy_msg": '请享受%%云存储服务及其他优惠。', // In English: "Enjoy %% of Cloud Storage plus other benefits."
}
};
diff --git a/src/gui/src/i18n/translations/zhtw.js b/src/gui/src/i18n/translations/zhtw.js
index 5fdda1b378..00b31d178d 100644
--- a/src/gui/src/i18n/translations/zhtw.js
+++ b/src/gui/src/i18n/translations/zhtw.js
@@ -360,6 +360,54 @@ const zhtw = {
"Owner": '所有者', // In English: "Owner"
"You can't share with yourself.": '您不能與自己分享。', // In English: "You can't share with yourself."
"This user already has access to this item": '該用戶已有訪問此項的權限', // In English: "This user already has access to this item"
+
+ "billing.change_payment_method": "更改", // Change
+ "billing.cancel": "取消", // Cancel
+ "billing.download_invoice": "下載發票", // Download
+ "billing.payment_method": "付款方式", // Payment Method
+ "billing.payment_method_updated": "付款方式已更新!", // Payment method updated!
+ "billing.confirm_payment_method": "確認付款方式", // Confirm Payment Method
+ "billing.payment_history": "付款記錄", // Payment History
+ "billing.refunded": "已退款", // Refunded
+ "billing.paid": "已付款", // Paid
+ "billing.ok": "確定", // OK
+ "billing.resume_subscription": "恢復訂閱", // Resume Subscription
+ "billing.subscription_cancelled": "您的訂閱已被取消。", // Your subscription has been canceled.
+ "billing.subscription_cancelled_description": "在本計費週期結束之前,您仍然可以使用您的訂閱。", // You will still have access to your subscription until the end of this billing period.
+ "billing.offering.free": "免費", // Free
+ "billing.offering.pro": "專業版", // Professional
+ "billing.offering.business": "商業版", // Business
+ "billing.cloud_storage": "雲端儲存空間", // Cloud Storage
+ "billing.ai_access": "AI 使用權限", // AI Access
+ "billing.bandwidth": "頻寬", // Bandwidth
+ "billing.apps_and_games": "應用程式與遊戲", // Apps & Games
+ "billing.upgrade_to_pro": "升級到 %strong%", // Upgrade to %strong%
+ "billing.switch_to": "切換到 %strong%", // Switch to %strong%
+ "billing.payment_setup": "付款設定", // Payment Setup
+ "billing.back": "返回", // Back
+ "billing.you_are_now_subscribed_to": "您現在的訂閱等級是 %strong%。", // You are now subscribed to %strong% tier.
+ "billing.you_are_now_subscribed_to_without_tier": "您現在是訂閱狀態", // You are now subscribed
+ "billing.subscription_cancellation_confirmation": "您確定要取消訂閱嗎?", // Are you sure you want to cancel your subscription?
+ "billing.subscription_setup": "訂閱設定", // Subscription Setup
+ "billing.cancel_it": "取消", // Cancel It
+ "billing.keep_it": "保留", // Keep It
+ "billing.subscription_resumed": "您的 %strong% 訂閱已恢復!", // Your %strong% subscription has been resumed!
+ "billing.upgrade_now": "立即升級", // Upgrade Now
+ "billing.upgrade": "升級", // Upgrade
+ "billing.currently_on_free_plan": "您目前使用的是免費方案。", // You are currently on the free plan.
+ "billing.download_receipt": "下載收據", // Download Receipt
+ "billing.subscription_check_error": "無法檢查您的訂閱狀態,請稍後再試。", // A problem occurred while checking your subscription status.
+ "billing.email_confirmation_needed": "您的電子郵件尚未確認。我們會向您發送驗證碼以進行確認。", // Your email has not been confirmed. We'll send you a code to confirm it now.
+ "billing.sub_cancelled_but_valid_until": "您已取消訂閱,訂閱將在計費週期結束後自動轉為免費方案。除非重新訂閱,否則不會再次收費。", // 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": "您的目前方案將持續到本計費週期結束。", // Your current plan until the end of this billing period.
+ "billing.current_plan": "目前方案", // Current plan
+ "billing.cancelled_subscription_tier": "已取消的訂閱(%%)", // Cancelled Subscription (%%)
+ "billing.manage": "管理", // Manage
+ "billing.limited": "有限", // Limited
+ "billing.expanded": "擴展", // Expanded
+ "billing.accelerated": "加速", // Accelerated
+ "billing.enjoy_msg": "享受 %% 的雲端儲存空間及其他福利。", // Enjoy %% of Cloud Storage plus other benefits.
+
}
};
diff --git a/src/gui/src/initgui.js b/src/gui/src/initgui.js
index cbcf765f7b..f2ed081c97 100644
--- a/src/gui/src/initgui.js
+++ b/src/gui/src/initgui.js
@@ -192,7 +192,7 @@ window.initgui = async function(options){
// Appends a viewport meta tag to the head of the document, ensuring optimal display on mobile devices.
// This tag sets the width of the viewport to the device width, and locks the zoom level to 1 (prevents user scaling).
- $('head').append(``);
+ $('head').append(``);
// GET query params provided
window.url_query_params = new URLSearchParams(window.location.search);
@@ -393,6 +393,8 @@ window.initgui = async function(options){
else if(window.url_query_params.has('auth_token')){
let query_param_auth_token = window.url_query_params.get('auth_token');
+ puter.setAuthToken(query_param_auth_token);
+
try{
whoami = await puter.os.user();
}catch(e){
@@ -1247,33 +1249,6 @@ window.initgui = async function(options){
clearTimeout(this.long_hover_timeout);
})
- // if an element has the .long-hover class, cancel the long-hover event if the mouse leaves
- $(document).on('paste', function(event){
- event = event.originalEvent ?? event;
-
- let clipboardData = event.clipboardData || window.clipboardData;
- let items = clipboardData.items || clipboardData.files;
-
- // return if paste is on input or textarea
- if($(event.target).is('input') || $(event.target).is('textarea'))
- return;
-
- if(!(items instanceof DataTransferItemList))
- return;
-
- // upload files
- if(items?.length>0){
- let parent_container = determine_active_container_parent();
- if(parent_container){
- window.upload_items(items, $(parent_container).attr('data-path'));
- }
- }
-
- event.stopPropagation();
- event.preventDefault();
- return false;
- })
-
document.addEventListener("visibilitychange", (event) => {
if (document.visibilityState !== "visible") {
window.doc_title_before_blur = document.title;
diff --git a/src/gui/src/keyboard.js b/src/gui/src/keyboard.js
index 44f3d7ea6a..da75e3513e 100644
--- a/src/gui/src/keyboard.js
+++ b/src/gui/src/keyboard.js
@@ -21,6 +21,7 @@ import UIAlert from './UI/UIAlert.js';
import UIWindowSearch from './UI/UIWindowSearch.js';
import launch_app from './helpers/launch_app.js';
import open_item from './helpers/open_item.js';
+import determine_active_container_parent from './helpers/determine_active_container_parent.js';
$(document).bind('keydown', async function(e){
const focused_el = document.activeElement;
diff --git a/src/gui/src/services/LocaleService.js b/src/gui/src/services/LocaleService.js
index b165d07d2b..b12c6c5486 100644
--- a/src/gui/src/services/LocaleService.js
+++ b/src/gui/src/services/LocaleService.js
@@ -21,7 +21,6 @@ import i18n from "../i18n/i18n.js";
export class LocaleService extends Service {
format_duration (seconds) {
- console.log('seconds?', typeof seconds, seconds);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
diff --git a/src/parsers/exports.js b/src/parsers/exports.js
new file mode 100644
index 0000000000..bcda36c0ff
--- /dev/null
+++ b/src/parsers/exports.js
@@ -0,0 +1,5 @@
+import * as strataparse_ from './strataparse/exports.js';
+import * as parsely_ from './parsely/exports.js';
+
+export const strataparse = strataparse_;
+export const parsely = parsely_;
diff --git a/src/parsely/package.json b/src/parsers/package.json
similarity index 81%
rename from src/parsely/package.json
rename to src/parsers/package.json
index 7ae60da350..9c3309a55e 100644
--- a/src/parsely/package.json
+++ b/src/parsers/package.json
@@ -1,5 +1,5 @@
{
- "name": "@heyputer/parsely",
+ "name": "@heyputer/parsers",
"version": "1.0.0",
"author": "Puter Technologies Inc.",
"license": "AGPL-3.0-only",
diff --git a/src/parsely/exports.js b/src/parsers/parsely/exports.js
similarity index 91%
rename from src/parsely/exports.js
rename to src/parsers/parsely/exports.js
index da80f40e98..7ee9a04dde 100644
--- a/src/parsely/exports.js
+++ b/src/parsers/parsely/exports.js
@@ -16,10 +16,19 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { adapt_parser, VALUE } from './parser.js';
+import * as parserjs from './parser.js';
+import * as streamsjs from './streams.js';
import { Discard, FirstMatch, Optional, Repeat, Sequence } from './parsers/combinators.js';
import { Fail, Literal, None, StringOf, StringUntil, Symbol } from './parsers/terminals.js';
+export const adapt_parser = parserjs.adapt_parser;
+export const Parser = parserjs.Parser;
+export const UNRECOGNIZED = parserjs.UNRECOGNIZED;
+export const INVALID = parserjs.INVALID;
+export const VALUE = parserjs.VALUE;
+
+export const streams = streamsjs;
+
class ParserWithAction {
#parser;
#action;
diff --git a/src/parsely/parser.js b/src/parsers/parsely/parser.js
similarity index 100%
rename from src/parsely/parser.js
rename to src/parsers/parsely/parser.js
diff --git a/src/parsely/parsers/combinators.js b/src/parsers/parsely/parsers/combinators.js
similarity index 100%
rename from src/parsely/parsers/combinators.js
rename to src/parsers/parsely/parsers/combinators.js
diff --git a/src/parsely/parsers/terminals.js b/src/parsers/parsely/parsers/terminals.js
similarity index 100%
rename from src/parsely/parsers/terminals.js
rename to src/parsers/parsely/parsers/terminals.js
diff --git a/src/parsely/streams.js b/src/parsers/parsely/streams.js
similarity index 100%
rename from src/parsely/streams.js
rename to src/parsers/parsely/streams.js
diff --git a/src/strataparse/dsl/ParserBuilder.js b/src/parsers/strataparse/dsl/ParserBuilder.js
similarity index 100%
rename from src/strataparse/dsl/ParserBuilder.js
rename to src/parsers/strataparse/dsl/ParserBuilder.js
diff --git a/src/strataparse/dsl/ParserRegistry.js b/src/parsers/strataparse/dsl/ParserRegistry.js
similarity index 100%
rename from src/strataparse/dsl/ParserRegistry.js
rename to src/parsers/strataparse/dsl/ParserRegistry.js
diff --git a/src/strataparse/exports.js b/src/parsers/strataparse/exports.js
similarity index 97%
rename from src/strataparse/exports.js
rename to src/parsers/strataparse/exports.js
index 42c46f02bc..e5c3e176fe 100644
--- a/src/strataparse/exports.js
+++ b/src/parsers/strataparse/exports.js
@@ -29,6 +29,10 @@ import WhitespaceParserImpl from './parse_impls/whitespace.js';
import LiteralParserImpl from './parse_impls/literal.js';
import StrUntilParserImpl from './parse_impls/StrUntilParserImpl.js';
+export {
+ MergeWhitespacePStratumImpl,
+} from './strata_impls/MergeWhitespacePStratumImpl.js'
+
import {
SequenceParserImpl,
ChoiceParserImpl,
diff --git a/src/strataparse/package.json b/src/parsers/strataparse/package.json
similarity index 100%
rename from src/strataparse/package.json
rename to src/parsers/strataparse/package.json
diff --git a/src/strataparse/parse.js b/src/parsers/strataparse/parse.js
similarity index 100%
rename from src/strataparse/parse.js
rename to src/parsers/strataparse/parse.js
diff --git a/src/strataparse/parse_impls/StrUntilParserImpl.js b/src/parsers/strataparse/parse_impls/StrUntilParserImpl.js
similarity index 100%
rename from src/strataparse/parse_impls/StrUntilParserImpl.js
rename to src/parsers/strataparse/parse_impls/StrUntilParserImpl.js
diff --git a/src/strataparse/parse_impls/combinators.js b/src/parsers/strataparse/parse_impls/combinators.js
similarity index 100%
rename from src/strataparse/parse_impls/combinators.js
rename to src/parsers/strataparse/parse_impls/combinators.js
diff --git a/src/strataparse/parse_impls/literal.js b/src/parsers/strataparse/parse_impls/literal.js
similarity index 100%
rename from src/strataparse/parse_impls/literal.js
rename to src/parsers/strataparse/parse_impls/literal.js
diff --git a/src/strataparse/parse_impls/whitespace.js b/src/parsers/strataparse/parse_impls/whitespace.js
similarity index 100%
rename from src/strataparse/parse_impls/whitespace.js
rename to src/parsers/strataparse/parse_impls/whitespace.js
diff --git a/src/strataparse/strata.js b/src/parsers/strataparse/strata.js
similarity index 100%
rename from src/strataparse/strata.js
rename to src/parsers/strataparse/strata.js
diff --git a/src/strataparse/strata_impls/ContextSwitchingPStratumImpl.js b/src/parsers/strataparse/strata_impls/ContextSwitchingPStratumImpl.js
similarity index 100%
rename from src/strataparse/strata_impls/ContextSwitchingPStratumImpl.js
rename to src/parsers/strataparse/strata_impls/ContextSwitchingPStratumImpl.js
diff --git a/src/strataparse/strata_impls/FirstRecognizedPStratumImpl.js b/src/parsers/strataparse/strata_impls/FirstRecognizedPStratumImpl.js
similarity index 100%
rename from src/strataparse/strata_impls/FirstRecognizedPStratumImpl.js
rename to src/parsers/strataparse/strata_impls/FirstRecognizedPStratumImpl.js
diff --git a/src/strataparse/strata_impls/MergeWhitespacePStratumImpl.js b/src/parsers/strataparse/strata_impls/MergeWhitespacePStratumImpl.js
similarity index 100%
rename from src/strataparse/strata_impls/MergeWhitespacePStratumImpl.js
rename to src/parsers/strataparse/strata_impls/MergeWhitespacePStratumImpl.js
diff --git a/src/strataparse/strata_impls/terminals.js b/src/parsers/strataparse/strata_impls/terminals.js
similarity index 100%
rename from src/strataparse/strata_impls/terminals.js
rename to src/parsers/strataparse/strata_impls/terminals.js
diff --git a/src/phoenix/src/ansi-shell/ANSIShell.js b/src/phoenix/src/ansi-shell/ANSIShell.js
index 4d9cd47f70..4615b643e3 100644
--- a/src/phoenix/src/ansi-shell/ANSIShell.js
+++ b/src/phoenix/src/ansi-shell/ANSIShell.js
@@ -17,9 +17,6 @@
* along with this program. If not, see .
*/
import { ConcreteSyntaxError } from "./ConcreteSyntaxError.js";
-import { MultiWriter } from "./ioutil/MultiWriter.js";
-import { Coupler } from "./pipeline/Coupler.js";
-import { Pipe } from "./pipeline/Pipe.js";
import { Pipeline } from "./pipeline/Pipeline.js";
export class ANSIShell extends EventTarget {
diff --git a/src/phoenix/src/ansi-shell/parsing/PuterShellParser.js b/src/phoenix/src/ansi-shell/parsing/PuterShellParser.js
index 50eba17830..5d20a3e1cc 100644
--- a/src/phoenix/src/ansi-shell/parsing/PuterShellParser.js
+++ b/src/phoenix/src/ansi-shell/parsing/PuterShellParser.js
@@ -16,7 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { StrataParser, StringPStratumImpl } from "strataparse";
+import { strataparse } from '@heyputer/parsers';
+const { StrataParser, StringPStratumImpl } = strataparse;
import { buildParserFirstHalf } from "./buildParserFirstHalf.js";
import { buildParserSecondHalf } from "./buildParserSecondHalf.js";
diff --git a/src/phoenix/src/ansi-shell/parsing/buildParserFirstHalf.js b/src/phoenix/src/ansi-shell/parsing/buildParserFirstHalf.js
index 7666be3a90..12b64a0665 100644
--- a/src/phoenix/src/ansi-shell/parsing/buildParserFirstHalf.js
+++ b/src/phoenix/src/ansi-shell/parsing/buildParserFirstHalf.js
@@ -16,11 +16,20 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { FirstRecognizedPStratumImpl, ParserBuilder, ParserFactory, StrUntilParserImpl, StrataParseFacade, WhitespaceParserImpl } from "strataparse";
-import { UnquotedTokenParserImpl } from "./UnquotedTokenParserImpl.js";
+import { strataparse } from '@heyputer/parsers';
+const {
+ ParserBuilder,
+ ParserFactory,
+ StrUntilParserImpl,
+ StrataParseFacade,
+ WhitespaceParserImpl
+} = strataparse;
+
import { PARSE_CONSTANTS } from "./PARSE_CONSTANTS.js";
-import { MergeWhitespacePStratumImpl } from "strataparse/strata_impls/MergeWhitespacePStratumImpl.js";
-import ContextSwitchingPStratumImpl from "strataparse/strata_impls/ContextSwitchingPStratumImpl.js";
+const {
+ ContextSwitchingPStratumImpl,
+ MergeWhitespacePStratumImpl,
+} = strataparse;
const parserConfigProfiles = {
syntaxHighlighting: { cst: true },
diff --git a/src/phoenix/src/ansi-shell/parsing/buildParserSecondHalf.js b/src/phoenix/src/ansi-shell/parsing/buildParserSecondHalf.js
index 0215048515..a89ce3166f 100644
--- a/src/phoenix/src/ansi-shell/parsing/buildParserSecondHalf.js
+++ b/src/phoenix/src/ansi-shell/parsing/buildParserSecondHalf.js
@@ -16,7 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { ParserBuilder, ParserFactory, StrataParseFacade } from "strataparse"
+import { strataparse } from '@heyputer/parsers'
+const { ParserBuilder, ParserFactory, StrataParseFacade } = strataparse;
import { PARSE_CONSTANTS } from "./PARSE_CONSTANTS.js";
const escapeSubstitutions = PARSE_CONSTANTS.escapeSubstitutions;
diff --git a/src/phoenix/src/ansi-shell/pipeline/Pipeline.js b/src/phoenix/src/ansi-shell/pipeline/Pipeline.js
index bd6ba41278..3bb58426ae 100644
--- a/src/phoenix/src/ansi-shell/pipeline/Pipeline.js
+++ b/src/phoenix/src/ansi-shell/pipeline/Pipeline.js
@@ -17,7 +17,6 @@
* along with this program. If not, see .
*/
import { SyncLinesReader } from "../ioutil/SyncLinesReader.js";
-import { TOKENS } from "../readline/readtoken.js";
import { ByteWriter } from "../ioutil/ByteWriter.js";
import { Coupler } from "./Coupler.js";
import { CommandStdinDecorator } from "./iowrappers.js";
diff --git a/src/phoenix/src/main_cli.js b/src/phoenix/src/main_cli.js
index 6bf740553e..620b999ece 100644
--- a/src/phoenix/src/main_cli.js
+++ b/src/phoenix/src/main_cli.js
@@ -16,7 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { Context } from 'contextlink';
+import { libs } from '@heyputer/putility';
+const { Context } = libs.context;
import { launchPuterShell } from './puter-shell/main.js';
import { NodeStdioPTT } from './pty/NodeStdioPTT.js';
import { CreateFilesystemProvider } from './platform/node/filesystem.js';
diff --git a/src/phoenix/src/main_puter.js b/src/phoenix/src/main_puter.js
index 3062b02529..dbbeecd0ed 100644
--- a/src/phoenix/src/main_puter.js
+++ b/src/phoenix/src/main_puter.js
@@ -16,7 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { Context } from 'contextlink';
+import { libs } from '@heyputer/putility';
+const { Context } = libs.context;
import { launchPuterShell } from './puter-shell/main.js';
import { CreateFilesystemProvider } from './platform/puter/filesystem.js';
import { CreateDriversProvider } from './platform/puter/drivers.js';
diff --git a/src/phoenix/src/platform/puter/filesystem.js b/src/phoenix/src/platform/puter/filesystem.js
index 22d939a29e..6356686381 100644
--- a/src/phoenix/src/platform/puter/filesystem.js
+++ b/src/phoenix/src/platform/puter/filesystem.js
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { ErrorCodes, PosixError } from '@heyputer/putility/src/PosixError.js';
+import { PosixError } from '@heyputer/putility/src/PosixError.js';
// DRY: Almost the same as node/filesystem.js
function wrapAPIs(apis) {
diff --git a/src/phoenix/src/puter-shell/coreutils/concept-parser.js b/src/phoenix/src/puter-shell/coreutils/concept-parser.js
index 1ed2ebd39f..894a6f5f51 100644
--- a/src/phoenix/src/puter-shell/coreutils/concept-parser.js
+++ b/src/phoenix/src/puter-shell/coreutils/concept-parser.js
@@ -16,9 +16,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { GrammarContext, standard_parsers } from '@heyputer/parsely/exports.js';
-import { Parser, UNRECOGNIZED, VALUE } from '@heyputer/parsely/parser.js';
-import { StringStream } from '@heyputer/parsely/streams.js';
+import { parsely } from '@heyputer/parsers';
+const { GrammarContext, standard_parsers } = parsely;
+const { Parser, UNRECOGNIZED, VALUE } = parsely;
+const { StringStream } = parsely.streams;
class NumberParser extends Parser {
static data = {
diff --git a/src/phoenix/src/puter-shell/coreutils/sed/parser.js b/src/phoenix/src/puter-shell/coreutils/sed/parser.js
index 68805cf525..e8d7e06af6 100644
--- a/src/phoenix/src/puter-shell/coreutils/sed/parser.js
+++ b/src/phoenix/src/puter-shell/coreutils/sed/parser.js
@@ -39,9 +39,10 @@ import {
ZapCommand,
} from './command.js';
import { Script } from './script.js';
-import { GrammarContext, standard_parsers } from '@heyputer/parsely/exports.js';
-import { StringStream } from '@heyputer/parsely/streams.js';
-import { Parser, UNRECOGNIZED, VALUE } from '@heyputer/parsely/parser.js';
+import { parsely } from '@heyputer/parsers';
+const { GrammarContext, standard_parsers } = parsely;
+const { StringStream } = parsely.streams;
+const { Parser, UNRECOGNIZED, VALUE } = parsely;
/**
* A slight hack: Parsely doesn't yet have an equivalent of backreferences.
diff --git a/src/phoenix/src/puter-shell/coreutils/wc.js b/src/phoenix/src/puter-shell/coreutils/wc.js
index 71376c9a57..f173f013ef 100644
--- a/src/phoenix/src/puter-shell/coreutils/wc.js
+++ b/src/phoenix/src/puter-shell/coreutils/wc.js
@@ -16,7 +16,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { resolveRelativePath } from '../../util/path.js';
import { fileLines } from '../../util/file.js';
const TAB_SIZE = 8;
diff --git a/src/phoenix/src/puter-shell/main.js b/src/phoenix/src/puter-shell/main.js
index 5eb89d2665..e4208f9ba7 100644
--- a/src/phoenix/src/puter-shell/main.js
+++ b/src/phoenix/src/puter-shell/main.js
@@ -23,7 +23,8 @@ import ReadlineLib from "../ansi-shell/readline/readline.js";
import SimpleArgParser from "../ansi-shell/arg-parsers/simple-parser.js";
import ErrorsDecorator from "../ansi-shell/decorators/errors.js";
import { ANSIShell } from "../ansi-shell/ANSIShell.js";
-import { Context } from "contextlink";
+import { libs } from '@heyputer/putility';
+const { Context } = libs.context;
import { SHELL_VERSIONS } from "../meta/versions.js";
import { PuterShellParser } from "../ansi-shell/parsing/PuterShellParser.js";
import { BuiltinCommandProvider } from "./providers/BuiltinCommandProvider.js";
@@ -45,11 +46,6 @@ const decorator_registry = {
[ErrorsDecorator.name]: ErrorsDecorator
};
-const GH_LINK = {
- 'terminal': 'https://github.com/HeyPuter/puter/tree/main/packages/terminal',
- 'phoenix': 'https://github.com/HeyPuter/puter/tree/main/packages/phoenix',
-};
-
export const launchPuterShell = async (ctx) => {
const config = ctx.config;
const ptt = ctx.ptt;
diff --git a/src/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js b/src/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js
index a07839756e..7d78dcc7e9 100644
--- a/src/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js
+++ b/src/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js
@@ -18,7 +18,6 @@
*/
import { Exit } from '../coreutils/coreutil_lib/exit.js';
import { signals } from '../../ansi-shell/signals.js';
-import { TeePromise } from '../../promise.js';
const BUILT_IN_APPS = [
'explorer',
diff --git a/src/phoenix/src/puter-shell/providers/ScriptCommandProvider.js b/src/phoenix/src/puter-shell/providers/ScriptCommandProvider.js
index 2d994096bd..f90ad1a203 100644
--- a/src/phoenix/src/puter-shell/providers/ScriptCommandProvider.js
+++ b/src/phoenix/src/puter-shell/providers/ScriptCommandProvider.js
@@ -16,7 +16,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import path_ from "path-browserify";
import { Pipeline } from "../../ansi-shell/pipeline/Pipeline.js";
import { resolveRelativePath } from '../../util/path.js';
diff --git a/src/phoenix/src/util/statemachine.js b/src/phoenix/src/util/statemachine.js
index ce0d6c1176..a71de1e9a1 100644
--- a/src/phoenix/src/util/statemachine.js
+++ b/src/phoenix/src/util/statemachine.js
@@ -17,7 +17,8 @@
* along with this program. If not, see .
*/
import { disallowAccessToUndefined } from "./lang.js";
-import { Context } from "contextlink";
+import putility from '@heyputer/putility';
+const { Context } = putility.libs.context;
export class StatefulProcessor {
constructor (params) {
diff --git a/src/phoenix/test.js b/src/phoenix/test.js
index 6c5fded778..f72f90f582 100644
--- a/src/phoenix/test.js
+++ b/src/phoenix/test.js
@@ -16,11 +16,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import {
+import { strataparse } from '@heyputer/parsers'
+const {
StringPStratumImpl,
StrataParser,
ParserFactory,
-} from 'strataparse';
+} = strataparse;
import { buildParserFirstHalf } from './src/ansi-shell/parsing/buildParserFirstHalf.js';
import { buildParserSecondHalf } from './src/ansi-shell/parsing/buildParserSecondHalf.js';
diff --git a/src/phoenix/test/coreutils/harness.js b/src/phoenix/test/coreutils/harness.js
index 5bfc2cdbd5..1de0e93bc7 100644
--- a/src/phoenix/test/coreutils/harness.js
+++ b/src/phoenix/test/coreutils/harness.js
@@ -16,7 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-import { Context } from "contextlink";
+import putility from '@heyputer/putility';
+const { Context } = putility.libs.context;
import { SyncLinesReader } from '../../src/ansi-shell/ioutil/SyncLinesReader.js';
import { CommandStdinDecorator } from '../../src/ansi-shell/pipeline/iowrappers.js';
import { ReadableStream, WritableStream } from 'stream/web'
diff --git a/src/puter-js/src/modules/KV.js b/src/puter-js/src/modules/KV.js
index 7cc51f3a36..d49def1bb4 100644
--- a/src/puter-js/src/modules/KV.js
+++ b/src/puter-js/src/modules/KV.js
@@ -1,5 +1,18 @@
+import { TeePromise } from '@heyputer/putility/src/libs/promise.js';
import * as utils from '../lib/utils.js'
+const gui_cache_keys = [
+ 'has_set_default_app_user_permissions',
+ 'window_sidebar_width',
+ 'sidebar_items',
+ 'menubar_style',
+ 'user_preferences.auto_arrange_desktop',
+ 'user_preferences.show_hidden_files',
+ 'user_preferences.language',
+ 'user_preferences.clock_visible',
+ 'has_seen_welcome_window',
+];
+
class KV{
MAX_KEY_SIZE = 1024;
MAX_VALUE_SIZE = 400 * 1024;
@@ -16,6 +29,36 @@ class KV{
this.authToken = context.authToken;
this.APIOrigin = context.APIOrigin;
this.appID = context.appID;
+
+ this.gui_cached = new TeePromise();
+ this.gui_cache_init = new TeePromise();
+ (async () => {
+ await this.gui_cache_init;
+ this.gui_cache_init = null;
+ const resp = await fetch(`${this.APIOrigin}/drivers/call`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${this.authToken}`,
+ },
+ body: JSON.stringify({
+ interface: 'puter-kvstore',
+ method: 'get',
+ args: {
+ key: gui_cache_keys,
+ },
+ }),
+ });
+ const arr_values = await resp.json();
+ const obj = {};
+ for (let i = 0; i < gui_cache_keys.length; i++) {
+ obj[gui_cache_keys[i]] = arr_values.result[i];
+ }
+ this.gui_cached.resolve(obj);
+ setTimeout(() => {
+ this.gui_cached = null;
+ }, 4000);
+ })();
}
/**
@@ -68,7 +111,23 @@ class KV{
/**
* Resolves to the value if the key exists, or `undefined` if the key does not exist. Rejects with an error on failure.
*/
- get = utils.make_driver_method(['key'], 'puter-kvstore', undefined, 'get', {
+ async get (...args) {
+ // Condition for gui boot cache
+ if (
+ typeof args[0] === 'string' &&
+ gui_cache_keys.includes(args[0]) &&
+ this.gui_cached !== null
+ ) {
+ this.gui_cache_init && this.gui_cache_init.resolve();
+ const cache = await this.gui_cached;
+ return cache[args[0]];
+ }
+
+ // Normal get
+ return await this.get_(...args);
+ }
+
+ get_ = utils.make_driver_method(['key'], 'puter-kvstore', undefined, 'get', {
preprocess: (args)=>{
// key size cannot be larger than MAX_KEY_SIZE
if(args.key.length > this.MAX_KEY_SIZE){
diff --git a/src/puter-js/src/services/APIAccess.js b/src/puter-js/src/services/APIAccess.js
index 3471f735a6..9fc5af000f 100644
--- a/src/puter-js/src/services/APIAccess.js
+++ b/src/puter-js/src/services/APIAccess.js
@@ -27,8 +27,10 @@ export class APIAccessService extends putility.concepts.Service {
const self = this;
const o = {};
[
+ ['auth_token','auth_token'],
['authToken','auth_token'],
['APIOrigin','api_origin'],
+ ['api_origin','api_origin'],
].forEach(([k1,k2]) => {
Object.defineProperty(o, k1, {
get () {
diff --git a/src/puter-js/src/services/Filesystem.js b/src/puter-js/src/services/Filesystem.js
index 4554a4d53a..699dc9abe8 100644
--- a/src/puter-js/src/services/Filesystem.js
+++ b/src/puter-js/src/services/Filesystem.js
@@ -63,15 +63,21 @@ export class FilesystemService extends putility.concepts.Service {
this.fs_proxy_.delegate = this.fs_nocache_;
}
- initializeSocket () {
+ async initializeSocket () {
if (this.socket) {
this.socket.disconnect();
}
- this.socket = io(this.APIOrigin, {
- auth: {
- auth_token: this.authToken,
- }
+ const svc_apiAccess = this._.context.services.get('api-access');
+ const api_info = svc_apiAccess.get_api_info();
+
+ if ( api_info.api_origin === undefined ) {
+ // This will get called again later with updated information
+ return;
+ }
+
+ this.socket = io(api_info.api_origin, {
+ auth: { auth_token: api_info.auth_token }
});
this.bindSocketEvents();
diff --git a/src/putility/README.md b/src/putility/README.md
index 607532248c..0f1e93a4f4 100644
--- a/src/putility/README.md
+++ b/src/putility/README.md
@@ -6,6 +6,142 @@ more flexible, with an aim to avoid any significant complexity.
Each class in this module is best described as an _idea_:
+## Libraries
+
+Putility contains general purpose library functions.
+
+### `putility.libs.smol`
+
+A small ("smol") library with commonly useful utility functions. This was
+moved here from a utility class called SmolUtil that used to be in Puter's
+backend.
+
+#### `ensure_array(value)`
+
+Wraps a value in an array if that value is not an array already.
+
+#### `add(...v)`
+
+Variadic sum function; nothing more.
+
+#### `split(str, sep, options)`
+
+Split a string by the specified separator.
+
+The parameter `options` is optional. It provided, it must be an object
+and can have any of the following values:
+
+- `trim` - trim leading and trailing whitespace from each separated component
+- `discard_empty` - discard empty components
+
+It is recommended to enable `trim` when `discard_empty` is enabled to also
+remove whitespace-only strings.
+
+### `putility.libs.context`
+
+This library exports class **Context**. This provides a context object
+that works both in node and the browser.
+
+> **Note:** A lot of Puter's backend code uses a _different_ implementation
+> for Context that uses AsyncLocalStorage (only available in node)
+
+When creating a context you pass it an object with values that the context
+will hold:
+
+```javascript
+const ctx = new Context({
+ some_key: 'some value',
+});
+
+ctx.some_key; // works just like a regular object
+```
+
+You can create sub-contexts using Context**.sub()**:
+
+```javascript
+const a = new Context({
+ some_key: 'some value'
+});
+const b = a.sub({
+ another_key: 'another value'
+});
+
+b.another_key; // "another value"
+b.some_key; // "some value"
+
+a.some_key = 'changed';
+b.some_key; // "changed"
+```
+
+### `putility.libs.time`
+
+This library contains constants for time values in milliseconds.
+Available constants are: **DAY**, **HOUR**, **MINUTE**, **SECOND**, **MILLISECOND**.
+
+Please note that while DAY is a constant value of `86400000` milliseconds,
+an actual "day" may have 1000 more or 1000 less milliseconds due to the
+possibility of a leap second. This library does not account for leap seconds.
+
+### `putility.libs.string`
+
+#### `quote(text)`
+
+Wraps a string in backticks, escaping any present backticks as needed to
+disambiguate. Note that this is meant for human-readable text, so the exact
+solution to disambiguating backticks is allowed to change in the future.
+
+#### `osclink(url, text)`
+
+Wrap text in OSC escape code to output links in a terminal emulator.
+
+#### `format_as_usd(amount)`
+
+Formats a USD currency amount that may have fractional cents.
+
+### `putility.libs.promise`
+
+Utilities for working with promises.
+
+#### **TeePromise**
+
+Possibily the most useful utility, TeePromise is a Promise that implements
+externally-available `resolve()` and `reject()` methods. This is useful
+when using async/await syntax as it avoids unnecessary callback handling.
+
+```javascript
+const tp = new TeePromise();
+
+new bb = Busboy({ /* ... */ });
+
+// imagine you have lots of code here, that you don't want to
+// indent in a `new Promise((resolve, reject) => { ...` block
+
+bb.on('error', err => {
+ tp.reject(err);
+});
+bb.on('close', () => {
+ tp.resolve();
+})
+
+return {
+ // Imagine you have other values here that don't require waiting
+ // for the promise to resolve; handling this when a large portion
+ // of the code is wrapped in a Promise constructor is error-prone.
+ promise: tp,
+};
+```
+
+## Basees
+
+Putility implements a chain of base classes for general purpose use.
+Simply extend the **AdvancedBase** class to add functionality to your
+class such as traits and inheritance-merged static objects.
+
+If a class must extend some class outside of putility, then putility is
+not meant to support it. This is instead considered "utility code" - i.e.
+not part of the application structure that adheres to the design
+principles of putility.
+
### BasicBase
**BasicBase** is the idea that there should be a common way to
diff --git a/src/putility/index.js b/src/putility/index.js
index 16ca0b749b..164c7d9490 100644
--- a/src/putility/index.js
+++ b/src/putility/index.js
@@ -19,7 +19,7 @@
const { AdvancedBase } = require('./src/AdvancedBase');
const { Service } = require('./src/concepts/Service');
const { ServiceManager } = require('./src/system/ServiceManager');
-const { TTopics } = require('./src/traits/traits');
+const traits = require('./src/traits/traits');
module.exports = {
AdvancedBase,
@@ -31,11 +31,12 @@ module.exports = {
context: require('./src/libs/context'),
listener: require('./src/libs/listener'),
log: require('./src/libs/log'),
+ string: require('./src/libs/string'),
+ time: require('./src/libs/time'),
+ smol: require('./src/libs/smol'),
},
concepts: {
Service,
},
- traits: {
- TTopics,
- },
+ traits,
};
diff --git a/src/putility/src/libs/listener.js b/src/putility/src/libs/listener.js
index 1d54b93d17..1255fb7508 100644
--- a/src/putility/src/libs/listener.js
+++ b/src/putility/src/libs/listener.js
@@ -29,6 +29,7 @@ class MultiDetachable extends FeatureBase {
];
constructor() {
+ super();
this.delegates = [];
this.detached_ = false;
}
diff --git a/src/backend/src/util/smolutil.js b/src/putility/src/libs/smol.js
similarity index 99%
rename from src/backend/src/util/smolutil.js
rename to src/putility/src/libs/smol.js
index 12942eaf57..fb9c52e98f 100644
--- a/src/backend/src/util/smolutil.js
+++ b/src/putility/src/libs/smol.js
@@ -49,3 +49,4 @@ class SmolUtil {
}
module.exports = SmolUtil;
+
diff --git a/src/backend/src/util/strutil.js b/src/putility/src/libs/string.js
similarity index 66%
rename from src/backend/src/util/strutil.js
rename to src/putility/src/libs/string.js
index 4701fc4755..c871fc4800 100644
--- a/src/backend/src/util/strutil.js
+++ b/src/putility/src/libs/string.js
@@ -1,3 +1,4 @@
+// METADATA // {"def":"core.util.strutil","ai-params":{"service":"claude"},"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
@@ -16,9 +17,13 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-// Convenience function for quoting strings in error messages.
-// Turns a string like this: some`value`
-// Into a string like this: `some\`value\``
+
+/**
+* Quotes a string value, handling special cases for undefined, null, functions, objects and numbers.
+* Escapes quotes and returns a JSON-stringified version with quote character normalization.
+* @param {*} str - The value to quote
+* @returns {string} The quoted string representation
+*/
const quot = (str) => {
if ( str === undefined ) return '[undefined]';
if ( str === null ) return '[null]';
@@ -34,11 +39,24 @@ const quot = (str) => {
return str;
}
+
+/**
+* Creates an OSC 8 hyperlink sequence for terminal output
+* @param {string} url - The URL to link to
+* @param {string} [text] - Optional display text, defaults to URL if not provided
+* @returns {string} Terminal escape sequence containing the hyperlink
+*/
const osclink = (url, text) => {
if ( ! text ) text = url;
return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
}
+
+/**
+* Formats a number as a USD currency string with appropriate decimal places
+* @param {number} amount - The amount to format
+* @returns {string} The formatted USD string
+*/
const format_as_usd = (amount) => {
if ( amount < 0.01 ) {
if ( amount < 0.00001 ) {
@@ -54,4 +72,5 @@ module.exports = {
quot,
osclink,
format_as_usd,
-};
\ No newline at end of file
+};
+
diff --git a/src/backend/src/util/time.js b/src/putility/src/libs/time.js
similarity index 99%
rename from src/backend/src/util/time.js
rename to src/putility/src/libs/time.js
index 17f670141b..a24b4f0976 100644
--- a/src/backend/src/util/time.js
+++ b/src/putility/src/libs/time.js
@@ -49,3 +49,4 @@ module.exports = {
HOUR,
DAY,
};
+
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 0000000000..e74f2f05a2
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,53 @@
+# Tools Directory
+
+This directory contains tools for developing and running puter.
+Each directory inside `/tools` is an npm workspace, so it can have its own
+package.json file and dependencies.
+
+## Scripts
+
+### `run-selfhosted.js`
+
+This is the main script for running a local instance of Puter.
+It verifies the version of node.js you are running and attempts to explain
+any errors that come up if initiating boot fails.
+
+Puter is booted with essential modules, and modules required for local
+file storage.
+
+### `gen-release-notes.js`
+
+Generates release notes between a hard-coded pair of versions. These versions
+need to be modified manually in the script source before running.
+
+### `check-translations.js`
+
+Checks for missing translations in `src/gui/src/i18n/translations`
+
+## Utilities
+
+### `comment-writer`
+
+Generates comments in source files using generative AI via Puter's AI drivers.
+
+To use this:
+- `cd` into the `tools/comment-writer` directory
+- Edit `config.json` and replace `auth_token` with your own
+- Run with a specified direcotry; for example:
+ `node main.js ../../src/backend/services`
+
+### `module-docgen`
+
+Document a module.
+
+## Libraries
+
+### comment-parser
+
+This is a package used by the `license-headers` tool to process existing
+comments.
+
+### file-walker
+
+This is used by `license-headers` and `comment-writer` to walk through
+source files.
diff --git a/tools/check-translations.js b/tools/check-translations.js
index 17e5361e86..7cfc94f869 100644
--- a/tools/check-translations.js
+++ b/tools/check-translations.js
@@ -1,3 +1,4 @@
+// METADATA // {"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
@@ -25,7 +26,13 @@ function reportError(message) {
process.stderr.write(`❌ ${message}\n`);
}
-// Check that each translation file is recorded in `translations`
+/**
+* Verifies that all translation files in the translations directory are properly registered
+* in the translations object. Checks for required properties like name, code, and dictionary.
+* Reports errors if translations are missing, improperly configured, or have mismatched codes.
+* @async
+* @returns {Promise}
+*/
async function checkTranslationRegistrations() {
const files = await fs.promises.readdir('./src/gui/src/i18n/translations');
for (const fileName of files) {
@@ -53,7 +60,13 @@ async function checkTranslationRegistrations() {
}
}
-// Ensure that translations only contain keys that exist in the en dictionary
+/**
+* Validates that translation dictionaries only contain keys present in en.js
+*
+* Iterates through all translations (except English) and checks that each key in their
+* dictionary exists in the en.js dictionary. Reports errors for any keys that don't exist.
+* Skips validation if the translation dictionary is missing or invalid.
+*/
function checkTranslationKeys() {
const enDictionary = translations.en.dictionary;
@@ -72,7 +85,15 @@ function checkTranslationKeys() {
}
}
-// Ensure that all keys passed to i18n() exist in the en dictionary
+/**
+* Checks for usage of i18n() calls in source files and verifies that all translation keys exist in en.js
+*
+* Scans JavaScript files in specified source directories for i18n() function calls using regex.
+* Validates that each key used in these calls exists in the English translation dictionary.
+*
+* @async
+* @returns {Promise}
+*/
async function checkTranslationUsage() {
const enDictionary = translations.en.dictionary;
diff --git a/tools/comment-parser/main.js b/tools/comment-parser/main.js
index 185cab0936..ead8fc1cd0 100644
--- a/tools/comment-parser/main.js
+++ b/tools/comment-parser/main.js
@@ -1,3 +1,4 @@
+// METADATA // {"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
@@ -44,6 +45,14 @@ lib.dedent_lines = lines => {
}
};
+
+/**
+* Creates a StringStream object for parsing a string with position tracking
+* @param {string} str - The string to parse
+* @param {Object} [options] - Optional configuration object
+* @param {Object} [options.state_] - Initial state with position
+* @returns {Object} StringStream instance with parsing methods
+*/
const StringStream = (str, { state_ } = {}) => {
const state = state_ ?? { pos: 0 };
return {
@@ -190,6 +199,13 @@ const BlockCommentParser = ({
};
};
+
+/**
+* Creates a writer for line-style comments with a specified prefix
+* @param {Object} options - Configuration options
+* @param {string} options.prefix - The prefix to use for each comment line
+* @returns {Object} A comment writer object
+*/
const LinesCommentWriter = ({ prefix }) => {
return {
write: (lines) => {
@@ -202,6 +218,15 @@ const LinesCommentWriter = ({ prefix }) => {
};
};
+
+/**
+* Creates a block comment writer with specified start/end markers and prefix
+* @param {Object} options - Configuration options
+* @param {string} options.start - Comment start marker (e.g. "/*")
+* @param {string} options.end - Comment end marker (e.g. "* /")
+* @param {string} options.prefix - Line prefix within comment (e.g. " * ")
+* @returns {Object} Block comment writer object
+*/
const BlockCommentWriter = ({ start, end, prefix }) => {
return {
write: (lines) => {
@@ -217,6 +242,15 @@ const BlockCommentWriter = ({ start, end, prefix }) => {
};
};
+
+/**
+* Creates a new CommentParser instance for parsing and handling source code comments
+*
+* @returns {Object} An object with methods:
+* - supports: Checks if a file type is supported
+* - extract_top_comments: Extracts comments from source code
+* - output_comment: Formats and outputs comments in specified style
+*/
const CommentParser = () => {
const registry_ = {
object: {
@@ -263,6 +297,13 @@ const CommentParser = () => {
};
+
+ /**
+ * Gets the language configuration for a given filename by extracting and validating its extension
+ * @param {Object} params - The parameters object
+ * @param {string} params.filename - The filename to get the language for
+ * @returns {Object} Object containing the language configuration
+ */
const get_language_by_filename = ({ filename }) => {
const { language } = (({ filename }) => {
const { language_id } = (({ filename }) => {
@@ -293,6 +334,13 @@ const CommentParser = () => {
return { language };
}
+
+ /**
+ * Checks if a given filename is supported by the comment parser
+ * @param {Object} params - The parameters object
+ * @param {string} params.filename - The filename to check support for
+ * @returns {boolean} Whether the file type is supported
+ */
const supports = ({ filename }) => {
try {
get_language_by_filename({ filename });
@@ -338,6 +386,15 @@ const CommentParser = () => {
return results;
}
+
+ /**
+ * Outputs a comment in the specified style for a given filename and text
+ * @param {Object} params - The parameters object
+ * @param {string} params.filename - The filename to determine comment style
+ * @param {string} params.style - The comment style to use ('lines' or 'block')
+ * @param {string} params.text - The text content of the comment
+ * @returns {string} The formatted comment string
+ */
const output_comment = ({ filename, style, text }) => {
const { language } = get_language_by_filename({ filename });
diff --git a/tools/comment-writer/main.js b/tools/comment-writer/main.js
index bf79364e26..f0ea502679 100644
--- a/tools/comment-writer/main.js
+++ b/tools/comment-writer/main.js
@@ -1,3 +1,4 @@
+// METADATA // {"ai-params":{"service":"claude"},"comment-verbosity": "high","ai-commented":{"service":"claude"}}
const enq = require('enquirer');
const wrap = require('word-wrap');
const dedent = require('dedent');
@@ -21,6 +22,36 @@ const FILE_EXCLUDES = [
/^eslint\.config\.js$/,
];
+const models_to_try = [
+ {
+ service: 'openai-completion',
+ model: 'gpt-4o',
+ },
+ {
+ service: 'claude',
+ },
+ {
+ service: 'xai',
+ },
+ // === Models that didn't work for this ===
+ // Sometimes outputs source lines despite "no surrounding text" instruction
+ // {
+ // service: 'openai-completion',
+ // model: 'gpt-4o-mini',
+ // },
+ // Was the first to break a source file
+ // {
+ // service: 'together-ai',
+ // model: 'meta-llama/Meta-Llama-3-70B-Instruct-Turbo',
+ // },
+ // Occasionally fails spectacularly
+ // {
+ // service: 'mistral',
+ // model: 'mistral-large-latest',
+ // }
+];
+
+
const axi = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false
@@ -34,15 +65,37 @@ context.config = JSON.parse(
fs.readFileSync('config.json')
);
+
+/**
+* @class AI
+* @description A class that handles interactions with the Puter API for AI-powered chat completions.
+* This class provides an interface to make requests to the Puter chat completion service,
+* handling authentication and message formatting. It supports various AI models through
+* the puter-chat-completion driver interface.
+*/
class AI {
constructor (context) {
//
}
- async complete ({ messages }) {
+
+ /**
+ * Sends a chat completion request to the Puter API and returns the response message.
+ *
+ * @param {Object} params - The parameters for the completion request
+ * @param {Array} params.messages - Array of message objects to send to the API
+ * @param {Object} params.driver_params - Additional parameters for the driver interface
+ * @returns {Promise} The response message from the API
+ *
+ * Makes a POST request to the configured API endpoint with the provided messages and
+ * driver parameters. Authenticates using the configured auth token and returns the
+ * message content from the response.
+ */
+ async complete ({ messages, driver_params }) {
const response = await axi.post(`${context.config.api_url}/drivers/call`, {
interface: 'puter-chat-completion',
method: 'complete',
+ ...driver_params,
args: {
messages,
},
@@ -58,10 +111,28 @@ class AI {
}
}
-class CommentWriter {
- //
+const ai_message_to_lines = text => {
+ // Extract text content from message object, handling various formats
+ while ( typeof text === 'object' ) {
+ if ( Array.isArray(text) ) text = text[0];
+ else if ( text.content ) text = text.content;
+ else if ( text.text ) text = text.text;
+ else {
+ console.log('Invalid message object', text);
+ throw new Error('Invalid message object');
+ }
+ }
+ return text.split('\n');
}
+/**
+* @class JavascriptFileProcessor
+* @description A class responsible for processing JavaScript source files to identify and extract
+* various code definitions and structures. It analyzes the file content line by line using
+* configurable pattern matchers to detect classes, methods, functions, control structures,
+* and constants. The processor maintains context and parameters for consistent processing
+* across multiple files.
+*/
class JavascriptFileProcessor {
constructor (context, parameters) {
this.context = context;
@@ -70,12 +141,15 @@ class JavascriptFileProcessor {
process (lines) {
const definitions = [];
+ // Collect definitions by iterating through each line
for ( let i = 0 ; i < lines.length ; i++ ) {
const line = lines[i];
+ // Iterate through each line in the file
for ( const matcher of this.parameters.definition_matchers ) {
const match = matcher.pattern.exec(line);
console.log('match object', match);
+ // Check if there is a match for any of the definition patterns
if ( match ) {
definitions.push({
...matcher.handler(match),
@@ -115,6 +189,48 @@ const js_processor = new JavascriptFileProcessor(context, {
};
}
},
+ {
+ name: 'if',
+ pattern: /^\s*if\s*\(.*\)\s*{/,
+ /**
+ * Matches code patterns against a line to identify if it's an if statement
+ * @param {string} line - The line of code to check
+ * @returns {Object} Returns an object with type: 'if' if pattern matches
+ * @description Identifies if statements by matching the pattern /^\s*if\s*\(.*\)\s*{/
+ * This handles basic if statement syntax with optional whitespace and any condition
+ * within the parentheses
+ */
+ handler: () => {
+ return { type: 'if' };
+ }
+ },
+ {
+ name: 'while',
+ pattern: /^\s*while\s*\(.*\)\s*{/,
+ /**
+ * Matches lines that begin with a while loop structure.
+ * @param {void} - Takes no parameters
+ * @returns {Object} Returns an object with type: 'while' to indicate this is a while loop definition
+ * @description Used by the definition matcher system to identify while loop structures in code.
+ * The pattern looks for lines that start with optional whitespace, followed by 'while',
+ * followed by parentheses containing any characters, and ending with an opening curly brace.
+ */
+ handler: () => {
+ return { type: 'while' };
+ }
+ },
+ {
+ name: 'for',
+ pattern: /^\s*for\s*\(.*\)\s*{/,
+ /**
+ * Matches for loop patterns in code and returns a 'for' type definition.
+ * Used by the JavascriptFileProcessor to identify for loop structures.
+ * @returns {Object} An object with type 'for' indicating a for loop was found
+ */
+ handler: () => {
+ return { type: 'for' };
+ }
+ },
{
name: 'method',
pattern: /^\s*async .*\(.*\).*{/,
@@ -132,6 +248,14 @@ const js_processor = new JavascriptFileProcessor(context, {
pattern: /^\s*[A-Za-z_\$]+.*\(\).*{/,
handler: (match) => {
const [ , name ] = match;
+ // Extract method name from match array and handle special cases for 'if' and 'while'
+ if ( name === 'if' ) {
+ return { type: 'if' };
+ }
+ // Check if the name is 'while' and return appropriate type
+ if ( name === 'while' ) {
+ return { type: 'while' };
+ }
return {
type: 'method',
name,
@@ -159,7 +283,7 @@ const js_processor = new JavascriptFileProcessor(context, {
type: 'function',
scope: 'lexical',
name,
- args: args.split(',').map(arg => arg.trim()),
+ args: (args ?? '').split(',').map(arg => arg.trim()),
};
}
},
@@ -179,6 +303,16 @@ const js_processor = new JavascriptFileProcessor(context, {
],
});
+
+/**
+* Creates a limited view of the code file by showing specific ranges around key lines.
+* Takes an array of lines and key places (anchors with context ranges) and returns
+* a formatted string showing relevant code sections with line numbers and descriptions.
+* Merges overlapping ranges to avoid duplication.
+* @param {string[]} lines - Array of code lines from the file
+* @param {Object[]} key_places - Array of objects defining important locations and context
+* @returns {string} Formatted string containing the limited code view
+*/
const create_limited_view = (lines, key_places) => {
// Sort key places by starting line
key_places.sort((a, b) => {
@@ -190,6 +324,7 @@ const create_limited_view = (lines, key_places) => {
const visible_ranges = [];
// Create visible ranges for each key place
+ // Create visible ranges for each key place in the limited view
for ( const key_place of key_places ) {
const anchor = key_place.anchor;
const lines_above = key_place.lines_above;
@@ -209,12 +344,14 @@ const create_limited_view = (lines, key_places) => {
// Merge overlapping visible ranges
const merged_ranges = [];
+ // Iterate through each visible range and merge overlapping ones
for ( const range of visible_ranges ) {
range.comments = [{
anchor: range.anchor,
text: range.comment
}];
+ // If no merged ranges exist yet, add this range as the first one
if ( ! merged_ranges.length ) {
merged_ranges.push(range);
continue;
@@ -222,6 +359,7 @@ const create_limited_view = (lines, key_places) => {
const last_range = merged_ranges[merged_ranges.length - 1];
+ // Check if the current range overlaps with the last range in merged_ranges
if ( last_range.end >= range.start ) {
last_range.end = Math.max(last_range.end, range.end);
last_range.comments.push({
@@ -237,6 +375,7 @@ const create_limited_view = (lines, key_places) => {
let limited_view = '';
let previous_visible_range = null;
+ // Iterate through visible ranges and add line numbers and comments
for ( let i = 0 ; i < lines.length ; i++ ) {
const line = lines[i];
@@ -244,7 +383,9 @@ const create_limited_view = (lines, key_places) => {
if ( i === 22 ) debugger;
+ // Iterate through merged ranges to find which range contains the current line
for ( const range of merged_ranges ) {
+ // Check if current line is within any of the merged ranges
if ( i >= range.start && i < range.end ) {
visible_range = range;
break;
@@ -253,17 +394,21 @@ const create_limited_view = (lines, key_places) => {
// console.log('visible_range', visible_range, i);
+ // Check if this line is visible in the current range
if ( visible_range === null ) {
continue;
}
+ // Check if visible range is different from previous range
if ( visible_range !== previous_visible_range ) {
if ( i !== 0 ) limited_view += '\n';
+ // Check if we're starting a new visible range and add appropriate header
if ( visible_range.comments.length === 1 ) {
const comment = visible_range.comments[0];
limited_view += `window around line ${comment.anchor}: ${comment.text}\n`;
} else {
limited_view += `window around lines ${visible_range.comments.length} key lines:\n`;
+ // Iterate through visible range comments and add them to the limited view
for ( const comment of visible_range.comments ) {
limited_view += `- line ${comment.anchor}: ${comment.text}\n`;
}
@@ -289,14 +434,23 @@ const create_limited_view = (lines, key_places) => {
* lines: [ 'comment line 1', 'comment line 2', ... ]
* }
*/
+/**
+* Injects comments into an array of code lines at specified positions
+* @param {string[]} lines - Array of original file lines
+* @param {Object[]} comments - Array of comment objects specifying where and what to inject
+* @param {number} comments[].position - Line number where comment should be inserted
+* @param {string[]} comments[].lines - Array of comment text lines to insert
+*/
const inject_comments = (lines, comments) => {
// Sort comments in reverse order
comments.sort((a, b) => b.position - a.position);
// Inject comments into lines
+ // Inject comments into lines array based on comment objects
for ( const comment of comments ) {
// AI might have been stupid and added a comment above a blank line,
// despite that we told it not to do that. So we need to adjust the position.
+ // Adjust comment position if it would be above a blank line
while ( comment.position < lines.length && ! lines[comment.position].trim() ) {
comment.position++;
}
@@ -305,6 +459,17 @@ const inject_comments = (lines, comments) => {
console.log('????', comment.position, lines[comment.position], '|' + indentation + '|');
const comment_lines = comment.lines.map(line => `${indentation}${line}`);
lines.splice(comment.position, 0, ...comment_lines);
+
+ // If the first line of the comment lines starts with '/*`, ensure there is
+ // a blank line above it.
+
+ // Check if comment starts with '/*' to ensure proper spacing above JSDoc comments
+ if ( comment_lines[0].trim().startsWith('/*') ) {
+ // Check if comment starts with JSDoc style to add blank line above
+ if ( comment.position > 0 && lines[comment.position - 1].trim() === '' ) {
+ lines.splice(comment.position, 0, '');
+ }
+ }
}
}
@@ -318,6 +483,14 @@ textutil.format = text => {
context.ai = new AI(context);
+
+/**
+* Creates a new AI instance for handling chat completions
+* @param {Object} context - The application context object
+* @description Initializes an AI instance that interfaces with the Puter chat completion API.
+* The AI instance is used to generate comments and other text responses through the
+* chat completion interface.
+*/
const main = async () => {
// const message = await context.ai.complete({
// messages: [
@@ -354,44 +527,111 @@ const main = async () => {
excludes: FILE_EXCLUDES,
}, rootpath);
+ let i = 0, limit = undefined;
for await ( const value of walk_iter ) {
+ if ( limit !== undefined && i >= limit ) break;
+ i++;
+
+ // Exit after processing 12 files
if ( value.is_dir ) {
console.log('directory:', value.path);
continue;
}
+ // Check if file is not a JavaScript file and skip it
if ( ! value.name.endsWith('.js') ) {
continue;
}
console.log('file:', value.path);
const lines = fs.readFileSync(value.path, 'utf8').split('\n');
+ let metadata, has_metadata_line = false;
+ // Check if metadata line exists and parse it
if ( lines[0].startsWith('// METADATA // ') ) {
- const metadata = JSON.parse(lines[0].slice('// METADATA // '.length));
+ has_metadata_line = true;
+ metadata = JSON.parse(lines[0].slice('// METADATA // '.length));
+ // Check if metadata exists and has been parsed from the first line
if ( metadata['ai-commented'] ) {
console.log('File was already commented by AI; skipping...');
continue;
}
+ } else metadata = {};
+
+ let refs = null;
+ // Check if there are any references in the metadata
+ if ( metadata['ai-refs'] ) {
+ const relative_file_paths = metadata['ai-refs'];
+ // name of file is the key, value is the contents
+ const references = {};
+
+ let n = 0;
+ // Iterate through each relative file path in the metadata
+ for ( const relative_file_path of relative_file_paths ) {
+ n++;
+ const full_path = path_.join(path_.dirname(value.path), relative_file_path);
+ const ref_text = fs.readFileSync(full_path, 'utf8');
+ references[relative_file_path] = ref_text;
+ }
+
+ // Check if there are any references in the metadata and process them
+ if ( n === 1 ) {
+ refs = dedent(`
+ The following documentation contains relevant information about the code.
+ The code will follow after this documentation.
+ `);
+
+ refs += '\n\n' + dedent(references[Object.keys(references)[0]]);
+ } else if ( n > 2 ) {
+ refs = dedent(`
+ The following documentation contains relevant information about the code.
+ The code will follow after a number of documentation files.
+ `);
+
+ // Iterate through each key in the references object
+ for ( const key of Object.keys(references) ) {
+ refs += '\n\n' + dedent(references[key]);
+ }
+ }
}
- const action = await enq.prompt({
+ const action = limit === undefined ? await enq.prompt({
type: 'select',
name: 'action',
message: 'Select action:',
choices: [
'generate',
'skip',
+ 'all',
+ 'limit',
'exit',
]
- })
+ }) : 'generate';
+ // const action = 'generate';
+ // Check if user wants to exit the program
if ( action.action === 'exit' ) {
break;
}
+ // Skip if user chose to exit
if ( action.action === 'skip' ) {
continue;
}
+ if ( action.action === 'limit' ) {
+ limit = await enq.prompt({
+ type: 'input',
+ name: 'limit',
+ message: 'Enter limit:'
+ });
+ i = 1;
+ limit = Number(limit.limit);
+ }
+
+ if ( action.action === 'all' ) {
+ i = 1;
+ limit = Infinity;
+ }
+
const { definitions } = js_processor.process(lines);
const key_places = [];
key_places.push({
@@ -406,6 +646,7 @@ const main = async () => {
lines_below: 2,
comment: `Bottom of ${value.name}`
});
+ // Iterate through each definition and add comments based on its type
for ( const definition of definitions ) {
key_places.push({
anchor: definition.line,
@@ -446,7 +687,9 @@ const main = async () => {
});
const numbers = message.content.split(',').map(n => Number(n));
+ // Iterate through each number in the array of line numbers
for ( const n of numbers ) {
+ // Check if the line number is valid and not NaN before adding comment
if ( Number.isNaN(n) ) {
console.log('Invalid number:', n);
continue;
@@ -458,10 +701,12 @@ const main = async () => {
}
*/
+ // Iterate through each definition to add comments
for ( const def of definitions ) {
console.log('def?', def);
let instruction = '';
+ // Check if the line starts with an if statement and has curly braces
if ( def.type === 'class' ) {
instruction = dedent(`
Since the comment is going above a class definition, please write a JSDoc style comment.
@@ -469,6 +714,16 @@ const main = async () => {
`);
}
+ // Check if comment is for an if/while/for control structure
+ if ( def.type === 'if' || def.type === 'while' || def.type === 'for' ) {
+ if ( metadata['comment-verbosity'] !== 'high' ) continue;
+ instruction = dedent(`
+ Since the comment is going above a control structure, please write a short concise comment.
+ The comment should be only one or two lines long, and should use line comments.
+ `);
+ }
+
+ // Check if comment is going above a method definition
if ( def.type === 'method' ) {
instruction = dedent(`
Since the comment is going above a method, please write a JSDoc style comment.
@@ -477,6 +732,7 @@ const main = async () => {
`);
}
+ // Check if comment is for a constant definition and set appropriate instruction
if ( def.type === 'const' ) {
instruction = dedent(`
Since the comment is going above a constant definition, please write a comment that explains
@@ -490,7 +746,11 @@ const main = async () => {
instruction: instruction,
});
}
+
+ const driver_params = metadata['ai-params'] ??
+ models_to_try[Math.floor(Math.random() * models_to_try.length)];
+ // Iterate through each comment object to add comments to the code
for ( const comment of comments ) {
// This doesn't work very well yet
/*
@@ -514,12 +774,14 @@ const main = async () => {
]
});
+ // Check if the comment lines start with '/*' and ensure there's a blank line above it
if ( ranges_message.content.trim() !== 'none' ) {
const ranges = ranges_message.content.split(',').map(range => {
const [ start, end ] = range.split('-').map(n => Number(n));
return { start, end };
});
+ // Iterate through ranges and add key places for each range
for ( const range of ranges ) {
key_places.push({
anchor: range.start,
@@ -534,39 +796,51 @@ const main = async () => {
console.log(limited_view);
}
*/
-
+
+ const prompt =
+ dedent(`
+ Please write a comment to be added above line ${comment.position}.
+ Do not write any surrounding text; just the comment itself.
+ Please include comment markers. If the comment is on a class, function, or method, please use jsdoc style.
+ The code is written in JavaScript.
+ `).trim() +
+ (refs ? '\n\n' + dedent(refs) : '') +
+ (comment.instruction ? '\n\n' + dedent(comment.instruction) : '') +
+ '\n\n' + limited_view
+ ;
+
+ // console.log('prompt:', prompt);
+
const message = await context.ai.complete({
messages: [
{
role: 'user',
- content: dedent(`
- Please write a comment to be added above line ${comment.position}.
- Do not write any surrounding text; just the comment itself.
- Please include comment markers. If the comment is on a class, function, or method, please use jsdoc style.
- The code is written in JavaScript.
- `).trim() +
- (comment.instruction ? '\n\n' + dedent(comment.instruction) : '') +
- '\n\n' + limited_view
+ content: prompt
}
- ]
+ ],
+ driver_params,
});
console.log('message:', message);
- comment.lines = message.content.split('\n');
+ comment.lines = ai_message_to_lines(message.content);
// Remove leading and trailing blank lines
+ // Remove leading and trailing blank lines from comment lines array
while ( comment.lines.length && ! comment.lines[0].trim() ) {
comment.lines.shift();
}
+ // Remove trailing blank lines from comment lines array
while ( comment.lines.length && ! comment.lines[comment.lines.length - 1].trim() ) {
comment.lines.pop();
}
+ // Remove leading "```" or "```" lines
// Remove leading "```" or "```" lines
if ( comment.lines[0].startsWith('```') ) {
comment.lines.shift();
}
// Remove trailing "```" lines
+ // Remove trailing "```" lines if present
if ( comment.lines[comment.lines.length - 1].startsWith('```') ) {
comment.lines.pop();
}
@@ -579,8 +853,14 @@ const main = async () => {
console.log('--- lines ---');
console.log(lines);
+ // Check if file has metadata line and remove it before adding new metadata
+ if ( has_metadata_line ) {
+ lines.shift();
+ }
+
lines.unshift('// METADATA // ' + JSON.stringify({
- 'ai-commented': true,
+ ...metadata,
+ 'ai-commented': driver_params,
}));
// Write the modified file
diff --git a/tools/gen-release-notes.js b/tools/gen-release-notes.js
index e2f1e0271b..93178eb825 100644
--- a/tools/gen-release-notes.js
+++ b/tools/gen-release-notes.js
@@ -1,3 +1,4 @@
+// METADATA // {"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
@@ -18,6 +19,7 @@
*/
import { simpleGit } from 'simple-git';
+// GitHub repository URL for generating commit links in release notes
const REPO_URL = 'https://github.com/HeyPuter/puter';
const params = {
@@ -31,6 +33,7 @@ const git = simpleGit();
const log = await git.log({ from: params.from });
const commits = log.all;
+// Array of all commits from git log between specified versions
const CC_REGEX = /^([a-z0-9]+)(\([a-z0-9]+\))?:\s(.*)/;
const parse_conventional_commit = message => {
const parts = CC_REGEX.exec(message);
@@ -84,6 +87,10 @@ const scope_aliases = {
};
const complicated_cases = [
+ /**
+ * Handles special cases for commit message transformations
+ * @type {Array}
+ */
function fix_i18n ({ commit, meta }) {
if ( meta.type === 'fix' && meta.scope === 'i18n' ) {
meta.type = 'i18n';
diff --git a/tools/license-headers/main.js b/tools/license-headers/main.js
index 2a6a734edc..704a8dc76c 100644
--- a/tools/license-headers/main.js
+++ b/tools/license-headers/main.js
@@ -1,3 +1,4 @@
+// METADATA // {"ai-commented":{"service":"claude"}}
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
@@ -29,6 +30,15 @@ const { CommentParser } = require('../comment-parser/main');
const fs = require('fs');
const path_ = require('path');
+
+/**
+* Compares two license headers and returns their Levenshtein distance and formatted diff
+* @param {Object} params - The parameters object
+* @param {string} params.header1 - First header text to compare
+* @param {string} params.header2 - Second header text to compare
+* @param {boolean} [params.distance_only=false] - If true, only return distance without diff
+* @returns {Object} Object containing distance and formatted terminal diff
+*/
const CompareFn = ({ header1, header2, distance_only = false }) => {
// Calculate Levenshtein distance
@@ -64,6 +74,13 @@ const CompareFn = ({ header1, header2, distance_only = false }) => {
};
}
+/**
+* Creates a license checker instance that can compare and validate license headers
+* @param {Object} params - Configuration parameters
+* @param {Object} params.comment_parser - Comment parser instance to use
+* @param {string} params.desired_header - The expected license header text
+* @returns {Object} License checker instance with compare and supports methods
+*/
const LicenseChecker = ({
comment_parser,
desired_header,
@@ -197,6 +214,15 @@ const license_check_test = async ({ options }) => {
}
};
+
+/**
+* Executes the main command line interface for the license header tool.
+* Sets up Commander.js program with commands for checking and syncing license headers.
+* Handles configuration file loading and command execution.
+*
+* @async
+* @returns {Promise} Resolves when command execution is complete
+*/
const cmd_check_fn = async () => {
const comment_parser = CommentParser();
const license_checker = LicenseChecker({
@@ -335,6 +361,18 @@ const cmd_check_fn = async () => {
t.printTable();
};
+
+/**
+* Synchronizes license headers in source files by adding missing headers and handling conflicts
+*
+* Walks through files, checks for license headers, and:
+* - Adds headers to files missing them
+* - Prompts user to resolve conflicts when headers don't match
+* - Handles duplicate headers by allowing removal
+* - Tracks counts of different header statuses (ok, missing, conflict, etc)
+*
+* @returns {Promise} Resolves when synchronization is complete
+*/
const cmd_sync_fn = async () => {
const comment_parser = CommentParser();
const desired_header = fs.readFileSync(
@@ -547,6 +585,14 @@ const cmd_sync_fn = async () => {
t.printTable();
};
+
+/**
+* Main entry point for the license header tool.
+* Sets up command line interface using Commander and processes commands.
+* Handles 'check' and 'sync' commands for managing license headers in files.
+*
+* @returns {Promise} Resolves when command processing is complete
+*/
const main = async () => {
const { program } = require('commander');
const helptext = dedent(`
diff --git a/tools/module-docgen/defs.js b/tools/module-docgen/defs.js
new file mode 100644
index 0000000000..23eb0f0ce8
--- /dev/null
+++ b/tools/module-docgen/defs.js
@@ -0,0 +1,345 @@
+// METADATA // {"ai-commented":{"service":"claude"}}
+const dedent = require('dedent');
+const doctrine = require('doctrine');
+
+
+/**
+* Out class - A utility class for generating formatted text output
+* Provides methods for creating headings, line feeds, and text output
+*
+* ~~with a fluent interface.~~
+* ^ Nope, AI got this wrong but maybe it's a good idea to
+* make this a fluent interface
+*
+* The constructor returns a bound function that
+* maintains the output state and provides access to helper methods.
+*/
+class Out {
+ constructor () {
+ this.str = '';
+ const fn = this.out.bind(this);
+ fn.h = this.h.bind(this);
+ fn.lf = this.lf.bind(this);
+ fn.text = () => this.str;
+ return fn;
+ }
+
+ h (n, text) {
+ this.str += '#'.repeat(n) + ' ' + text + '\n\n';
+ }
+
+
+ /**
+ * Adds a line feed (newline) to the output string
+ * @returns {void}
+ */
+ lf () { this.str += '\n'; }
+
+ /**
+ * Append to the string
+ * @param {string} str
+ */
+ out (str) {
+ this.str += str;
+ }
+}
+
+
+/**
+* Doc class serves as a base class for documentation generation.
+* Provides core functionality for parsing and storing documentation comments
+* using the doctrine parser. Contains methods for handling JSDoc-style
+* comments and maintaining documentation state.
+*/
+class Doc {
+ constructor () {
+ this._construct();
+ }
+ provide_comment (comment) {
+ const parsed_comment = doctrine.parse(comment.value, { unwrap: true });
+ this.comment = parsed_comment.description;
+ }
+}
+
+
+/**
+* ModuleDoc class extends Doc to represent documentation for a module.
+* Handles module-level documentation including services, libraries, and requirements.
+* Provides methods for adding services/libraries and generating markdown documentation.
+* Tracks external imports and generates notes about module dependencies.
+*/
+class ModuleDoc extends Doc {
+ /**
+ * Initializes the base properties for a ModuleDoc instance
+ * Sets up empty arrays for services, requires, and libs collections
+ * @private
+ */
+ _construct () {
+ this.services = [];
+ this.requires = [];
+ this.libs = [];
+ }
+
+
+ /**
+ * Creates and adds a new service to this module's services array
+ * @returns {ServiceDoc} The newly created service document instance
+ */
+ add_service () {
+ const service = new ServiceDoc();
+ this.services.push(service);
+ return service;
+ }
+
+
+ /**
+ * Creates and adds a new LibDoc instance to the module's libs array
+ * @returns {LibDoc} The newly created LibDoc instance
+ */
+ add_lib () {
+ const lib = new LibDoc();
+ this.libs.push(lib);
+ return lib;
+ }
+
+
+ /**
+ * Populates a "notes" array for the module documentation
+ * based on findings about imports.
+ */
+ ready () {
+ this.notes = [];
+ const rel_requires = this.requires.filter(r => r.startsWith('../'));
+ if ( rel_requires.length > 0 ) {
+ this.notes.push({
+ title: 'Outside Imports',
+ desc: dedent(`
+ This module has external relative imports. When these are
+ removed it may become possible to move this module to an
+ extension.
+
+ **Imports:**
+ ${rel_requires.map(r => {
+ let maybe_aside = '';
+ if ( r.endsWith('BaseService') ) {
+ maybe_aside = ' (use.BaseService)';
+ }
+ return `- \`${r}\`` + maybe_aside;
+ }).join('\n')}
+ `)
+ });
+ }
+ }
+
+ toMarkdown ({ hl, out } = { hl: 1 }) {
+ this.ready();
+
+ out = out ?? new Out();
+
+ out.h(hl, this.name);
+
+ out(this.comment + '\n\n');
+
+ if ( this.services.length > 0 ) {
+ out.h(hl + 1, 'Services');
+
+ for ( const service of this.services ) {
+ service.toMarkdown({ out, hl: hl + 2 });
+ }
+ }
+
+ if ( this.libs.length > 0 ) {
+ out.h(hl + 1, 'Libraries');
+
+ for ( const lib of this.libs ) {
+ lib.toMarkdown({ out, hl: hl + 2 });
+ }
+ }
+
+ if ( this.notes.length > 0 ) {
+ out.h(hl + 1, 'Notes');
+ for ( const note of this.notes ) {
+ out.h(hl + 2, note.title);
+ out(note.desc);
+ out.lf();
+ }
+ }
+
+
+ return out.text();
+ }
+}
+
+
+/**
+* ServiceDoc class represents documentation for a service module.
+* Handles parsing and formatting of service-related documentation including
+* listeners, methods, and their associated parameters. Extends the base Doc class
+* to provide specialized documentation capabilities for service components.
+*/
+class ServiceDoc extends Doc {
+ /**
+ * Represents documentation for a service
+ * Handles parsing and storing service documentation including listeners and methods
+ * Initializes with empty arrays for listeners and methods
+ */
+ _construct () {
+ this.listeners = [];
+ this.methods = [];
+ }
+
+ provide_comment (comment) {
+ const parsed_comment = doctrine.parse(comment.value, { unwrap: true });
+ this.comment = parsed_comment.description;
+ }
+
+ provide_listener (listener) {
+ const parsed_comment = doctrine.parse(listener.comment, { unwrap: true });
+
+ const params = [];
+ for ( const tag of parsed_comment.tags ) {
+ if ( tag.title !== 'evtparam' ) continue;
+ const name = tag.description.slice(0, tag.description.indexOf(' '));
+ const desc = tag.description.slice(tag.description.indexOf(' '));
+ params.push({ name, desc })
+ }
+
+ this.listeners.push({
+ ...listener,
+ comment: parsed_comment.description,
+ params,
+ });
+ }
+
+ provide_method (method) {
+ const parsed_comment = doctrine.parse(method.comment, { unwrap: true });
+
+ const params = [];
+ for ( const tag of parsed_comment.tags ) {
+ if ( tag.title !== 'param' ) continue;
+ const name = tag.name;
+ const desc = tag.description;
+ params.push({ name, desc })
+ }
+
+ this.methods.push({
+ ...method,
+ comment: parsed_comment.description,
+ params,
+ });
+ }
+
+ toMarkdown ({ hl, out } = { hl: 1 }) {
+ out = out ?? new Out();
+
+ out.h(hl, this.name);
+
+ out(this.comment + '\n\n');
+
+ if ( this.listeners.length > 0 ) {
+ out.h(hl + 1, 'Listeners');
+
+ for ( const listener of this.listeners ) {
+ out.h(hl + 2, '`' + listener.key + '`');
+ out (listener.comment + '\n\n');
+
+ if ( listener.params.length > 0 ) {
+ out.h(hl + 3, 'Parameters');
+ for ( const param of listener.params ) {
+ out(`- **${param.name}:** ${param.desc}\n`);
+ }
+ out.lf();
+ }
+ }
+ }
+
+ if ( this.methods.length > 0 ) {
+ out.h(hl + 1, 'Methods');
+
+ for ( const method of this.methods ) {
+ out.h(hl + 2, '`' + method.key + '`');
+ out (method.comment + '\n\n');
+
+ if ( method.params.length > 0 ) {
+ out.h(hl + 3, 'Parameters');
+ for ( const param of method.params ) {
+ out(`- **${param.name}:** ${param.desc}\n`);
+ }
+ out.lf();
+ }
+ }
+ }
+
+ return out.text();
+ }
+}
+
+
+/**
+* LibDoc class for documenting library modules
+* Handles documentation for library functions including their descriptions,
+* parameters, and markdown generation. Extends the base Doc class to provide
+* specialized documentation capabilities for library components.
+*/
+class LibDoc extends Doc {
+ /**
+ * Represents documentation for a library module
+ *
+ * Handles parsing and formatting documentation for library functions.
+ * Stores function definitions with their comments, parameters and descriptions.
+ * Can output formatted markdown documentation.
+ */
+ _construct () {
+ this.functions = [];
+ }
+
+ provide_function ({ key, comment, params }) {
+ const parsed_comment = doctrine.parse(comment, { unwrap: true });
+
+ const parsed_params = [];
+ for ( const tag of parsed_comment.tags ) {
+ if ( tag.title !== 'param' ) continue;
+ const name = tag.name;
+ const desc = tag.description;
+ parsed_params.push({ name, desc });
+ }
+
+ this.functions.push({
+ key,
+ comment: parsed_comment.description,
+ params: parsed_params,
+ });
+ }
+
+ toMarkdown ({ hl, out } = { hl: 1 }) {
+ out = out ?? new Out();
+
+ out.h(hl, this.name);
+
+ console.log('functions?', this.functions);
+
+ if ( this.functions.length > 0 ) {
+ out.h(hl + 1, 'Functions');
+
+ for ( const func of this.functions ) {
+ out.h(hl + 2, '`' + func.key + '`');
+ out(func.comment + '\n\n');
+
+ if ( func.params.length > 0 ) {
+ out.h(hl + 3, 'Parameters');
+ for ( const param of func.params ) {
+ out(`- **${param.name}:** ${param.desc}\n`);
+ }
+ out.lf();
+ }
+ }
+ }
+
+ return out.text();
+ }
+}
+
+module.exports = {
+ ModuleDoc,
+ ServiceDoc,
+};
diff --git a/tools/module-docgen/main.js b/tools/module-docgen/main.js
new file mode 100644
index 0000000000..b9513663af
--- /dev/null
+++ b/tools/module-docgen/main.js
@@ -0,0 +1,112 @@
+const fs = require("fs");
+const path_ = require("path");
+
+const rootdir = path_.resolve(process.argv[2] ?? '.');
+
+const parser = require('@babel/parser');
+const traverse = require('@babel/traverse').default;
+const { ModuleDoc } = require("./defs");
+const processors = require("./processors");
+
+const doc_module = new ModuleDoc();
+
+const handle_file = (code, context) => {
+ const ast = parser.parse(code);
+
+ const traverse_callbacks = {};
+ for ( const processor of processors ) {
+ if ( processor.match(context) ) {
+ for ( const key in processor.traverse ) {
+ if ( ! traverse_callbacks[key] ) {
+ traverse_callbacks[key] = [];
+ }
+ traverse_callbacks[key].push(processor.traverse[key]);
+ }
+ }
+ }
+ for ( const key in traverse_callbacks ) {
+ traverse(ast, {
+ [key] (path) {
+ context.skip = false;
+ for ( const callback of traverse_callbacks[key] ) {
+ callback(path, context);
+ if ( context.skip ) return;
+ }
+ }
+ });
+ }
+}
+
+// Module and class files
+{
+ const files = fs.readdirSync(rootdir);
+ for ( const file of files ) {
+ const stat = fs.statSync(path_.join(rootdir, file));
+ if ( stat.isDirectory() ) {
+ continue;
+ }
+ if ( ! file.endsWith('.js') ) continue;
+
+ const type =
+ file.endsWith('Service.js') ? 'service' :
+ file.endsWith('Module.js') ? 'module' :
+ null;
+
+ if ( type === null ) continue;
+
+ console.log('file', file);
+ const code = fs.readFileSync(path_.join(rootdir, file), 'utf8');
+
+ const firstLine = code.slice(0, code.indexOf('\n'));
+ let metadata = {};
+ const METADATA_PREFIX = '// METADATA // ';
+ if ( firstLine.startsWith(METADATA_PREFIX) ) {
+ metadata = JSON.parse(firstLine.slice(METADATA_PREFIX.length));
+ }
+
+ const context = {
+ metadata,
+ type,
+ doc_module,
+ filename: file,
+ };
+
+ handle_file(code, context);
+ }
+}
+
+// Library files
+if ( fs.existsSync(path_.join(rootdir, 'lib')) ) {
+ const files = fs.readdirSync(path_.join(rootdir, 'lib'));
+ for ( const file of files ) {
+ if ( file.startsWith('_') ) continue;
+
+ const code = fs.readFileSync(path_.join(rootdir, 'lib', file), 'utf8');
+
+ const firstLine = code.slice(0, code.indexOf('\n'));
+ let metadata = {};
+ const METADATA_PREFIX = '// METADATA // ';
+ if ( firstLine.startsWith(METADATA_PREFIX) ) {
+ metadata = JSON.parse(firstLine.slice(METADATA_PREFIX.length));
+ }
+
+ const doc_item = doc_module.add_lib();
+ doc_item.name = metadata.def ?? file.slice(0, -3);
+
+ const context = {
+ metadata,
+ type: 'lib',
+ doc_module,
+ doc_item,
+ filename: file,
+ };
+
+ handle_file(code, context);
+ }
+}
+
+const outfile = path_.join(rootdir, 'README.md');
+
+const out = doc_module.toMarkdown();
+
+fs.writeFileSync(outfile, out);
\ No newline at end of file
diff --git a/tools/module-docgen/package.json b/tools/module-docgen/package.json
new file mode 100644
index 0000000000..4aa2bdd5b6
--- /dev/null
+++ b/tools/module-docgen/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "module-docgen",
+ "version": "1.0.0",
+ "main": "main.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "AGPL-3.0-only",
+ "description": "",
+ "dependencies": {
+ "@babel/parser": "^7.26.2",
+ "@babel/traverse": "^7.25.9",
+ "dedent": "^1.5.3",
+ "doctrine": "^3.0.0"
+ }
+}
diff --git a/tools/module-docgen/processors.js b/tools/module-docgen/processors.js
new file mode 100644
index 0000000000..3c51cc09a6
--- /dev/null
+++ b/tools/module-docgen/processors.js
@@ -0,0 +1,144 @@
+const processors = [];
+
+processors.push({
+ title: 'track all require calls',
+ match () { return true; },
+ traverse: {
+ CallExpression (path, context) {
+ const callee = path.get('callee');
+ if ( ! callee.isIdentifier() ) return;
+
+ if ( callee.node.name === 'require' ) {
+ context.doc_module.requires.push(path.node.arguments[0].value);
+ }
+ }
+ }
+});
+
+processors.push({
+ title: 'get leading comment',
+ match () { return true; },
+ traverse: {
+ ClassDeclaration (path, context) {
+ const node = path.node;
+ const comment = (node.leadingComments && (
+ node.leadingComments.length < 1 ? '' :
+ node.leadingComments[node.leadingComments.length - 1]
+ )) ?? '';
+ context.comment = comment;
+ }
+ }
+});
+
+processors.push({
+ title: 'provide name and comment for modules and services',
+ match (context) {
+ return context.type === 'module' || context.type === 'service';
+ },
+ traverse: {
+ ClassDeclaration (path, context) {
+ context.doc_item = context.doc_module;
+ if ( context.type === 'service' ) {
+ // Skip if class name doesn't end with 'Service'
+ if ( ! path.node.id.name.endsWith('Service') ) {
+ context.skip = true;
+ return;
+ }
+ context.doc_item = context.doc_module.add_service();
+ }
+ context.doc_item.name = path.node.id.name;
+ if ( context.comment === '' ) return;
+ context.doc_item.provide_comment(context.comment);
+ }
+ }
+});
+
+processors.push({
+ title: 'provide methods and listeners for services',
+ match (context) {
+ return context.type === 'service';
+ },
+ traverse: {
+ ClassDeclaration (path, context) {
+ path.node.body.body.forEach(member => {
+ if ( member.type !== 'ClassMethod' ) return;
+
+ const key = member.key.name ?? member.key.value;
+
+ const comment = member.leadingComments?.[0]?.value ?? '';
+
+ if ( key.startsWith('__on_') ) {
+ // 2nd argument is always an object destructuring;
+ // we want the list of keys in the object:
+ const params = member.params?.[1]?.properties ?? [];
+
+ context.doc_item.provide_listener({
+ key: key.slice(5),
+ comment,
+ params,
+ });
+ } else {
+ // Method overrides
+ if ( key.startsWith('_') ) return;
+
+ // Private methods
+ if ( key.endsWith('_') ) return;
+
+ const params = member.params ?? [];
+
+ context.doc_item.provide_method({
+ key,
+ comment,
+ params,
+ });
+ }
+ });
+ }
+ }
+});
+
+processors.push({
+ title: 'provide library function documentation',
+ match (context) {
+ return context.type === 'lib';
+ },
+ traverse: {
+ VariableDeclaration (path, context) {
+ // skip non-const declarations
+ if ( path.node.kind !== 'const' ) return;
+
+ // skip declarations with multiple declarators
+ if ( path.node.declarations.length !== 1 ) return;
+
+ // skip declarations without an initializer
+ if ( ! path.node.declarations[0].init ) return;
+
+ // skip declarations that aren't in the root scope
+ if ( path.scope.parent ) return;
+
+ console.log('path.node', path.node.declarations);
+
+ // is it a function?
+ if ( ! ['FunctionExpression', 'ArrowFunctionExpression'].includes(
+ path.node.declarations[0].init.type
+ ) ) return;
+
+ // get the name of the function
+ const name = path.node.declarations[0].id.name;
+
+ // get the comment
+ const comment = path.node.leadingComments?.[0]?.value ?? '';
+
+ // get the parameters
+ const params = path.node.declarations[0].init.params ?? [];
+
+ context.doc_item.provide_function({
+ key: name,
+ comment,
+ params,
+ });
+ }
+ }
+});
+
+module.exports = processors;
diff --git a/tools/run-selfhosted.js b/tools/run-selfhosted.js
index 9253b21ebe..8334be6f79 100644
--- a/tools/run-selfhosted.js
+++ b/tools/run-selfhosted.js
@@ -80,7 +80,7 @@ if ( ! import.meta.filename ) {
const main = async () => {
const {
Kernel,
- CoreModule,
+ EssentialModules,
DatabaseModule,
LocalDiskStorageModule,
SelfHostedModule,
@@ -92,7 +92,9 @@ const main = async () => {
const k = new Kernel({
entry_path: import.meta.filename
});
- k.add_module(new CoreModule());
+ for ( const mod of EssentialModules ) {
+ k.add_module(new mod());
+ }
k.add_module(new DatabaseModule());
k.add_module(new LocalDiskStorageModule());
k.add_module(new SelfHostedModule());