Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5303d63e4b | |||
| 05a30e1ec6 | |||
| b1a55785b5 | |||
| 24b47e9d4b | |||
| 34d19f9dbe | |||
| 95dddd7da0 | |||
| 1a949ecdc6 | |||
| 2e6f1c207c | |||
| 6aa0fa9465 | |||
| 8677df0340 | |||
| 125f6ac619 | |||
| 89ecf5c529 | |||
| fa78d6057f | |||
| cfc28be898 | |||
| c8efd4f9db | |||
| ada4e53b46 | |||
| 91494b0188 | |||
| e9fd6ec4d5 | |||
| f08f4058dc | |||
| d60200205a | |||
| de38eb2963 | |||
| f22dd4535d | |||
| 043589b301 | |||
| 4556827d79 | |||
| 98ebd6d7bc | |||
| 0a3ca4a1d4 | |||
| 106410f55a | |||
| 1ffe1b68a9 | |||
| 91ab0e609b | |||
| cbb7a666cd | |||
| e8cf14334f | |||
| 019790791b | |||
| e41ba2668f | |||
| 66a09fdc4b | |||
| e50143ca7e | |||
| 162b120e55 | |||
| b4dd47aa37 | |||
| 256c232a85 | |||
| b7ddf22662 | |||
| 5f60e9833e | |||
| ceed23ff51 |
@@ -36,6 +36,7 @@ PREVIEW_MAX_BUFFER=
|
|||||||
IMPORT_LIMIT=
|
IMPORT_LIMIT=
|
||||||
PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH=
|
PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH=
|
||||||
MAX_WORKERS=
|
MAX_WORKERS=
|
||||||
|
DISABLE_PRESERVATION=
|
||||||
|
|
||||||
# AWS S3 Settings
|
# AWS S3 Settings
|
||||||
SPACES_KEY=
|
SPACES_KEY=
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: Check pull request source branch
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- edited
|
||||||
|
jobs:
|
||||||
|
check-branches:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check branches
|
||||||
|
run: |
|
||||||
|
if [ ${{ github.head_ref }} != "dev" ] && [ ${{ github.base_ref }} == "main" ]; then
|
||||||
|
echo "Merge requests to main branch are only allowed from dev branch. Please rebase your changes to dev branch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
name: Create and publish a container image on release
|
name: Create and publish a container image on release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<a href="https://discord.com/invite/CtuYV47nuJ"><img src="https://img.shields.io/discord/1117993124669702164?logo=discord&style=flat" alt="Discord"></a>
|
<a href="https://discord.com/invite/CtuYV47nuJ"><img src="https://img.shields.io/discord/1117993124669702164?logo=discord&style=flat" alt="Discord"></a>
|
||||||
<a href="https://twitter.com/LinkwardenHQ"><img src="https://img.shields.io/twitter/follow/linkwarden" alt="Twitter"></a> <a href="https://news.ycombinator.com/item?id=36942308"><img src="https://img.shields.io/badge/Hacker%20News-280-%23FF6600"></img></a>
|
<a href="https://twitter.com/LinkwardenHQ"><img src="https://img.shields.io/twitter/follow/linkwarden" alt="Twitter"></a> <a href="https://news.ycombinator.com/item?id=36942308"><img src="https://img.shields.io/badge/Hacker%20News-280-%23FF6600"></img></a>
|
||||||
|
|
||||||
|
<a href="https://github.com/linkwarden/linkwarden/actions/workflows/release-container.yml"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/linkwarden/linkwarden/release-container.yml"></a>
|
||||||
|
<a href="https://opencollective.com/linkwarden"><img src="https://img.shields.io/opencollective/all/linkwarden" alt="Open Collective"></a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align='center'>
|
<div align='center'>
|
||||||
@@ -69,16 +72,19 @@ We've forked the old version from the current repository into [this repo](https:
|
|||||||
- 📱 Responsive design and supports most modern browsers.
|
- 📱 Responsive design and supports most modern browsers.
|
||||||
- 🌓 Dark/Light mode support.
|
- 🌓 Dark/Light mode support.
|
||||||
- 🧩 Browser extension, managed by the community. [Star it here!](https://github.com/linkwarden/browser-extension)
|
- 🧩 Browser extension, managed by the community. [Star it here!](https://github.com/linkwarden/browser-extension)
|
||||||
|
- 🔄 Browser Synchronization (using [Floccus](https://floccus.org)!)
|
||||||
- ⬇️ Import and export your bookmarks.
|
- ⬇️ Import and export your bookmarks.
|
||||||
- 🔐 SSO integration. (Enterprise and Self-hosted users only)
|
- 🔐 SSO integration. (Enterprise and Self-hosted users only)
|
||||||
- 📦 Installable Progressive Web App (PWA).
|
- 📦 Installable Progressive Web App (PWA).
|
||||||
- 🍏 iOS and MacOS Apps, maintained by [JGeek00](https://github.com/JGeek00).
|
- 🍏 iOS and MacOS Apps, maintained by [JGeek00](https://github.com/JGeek00).
|
||||||
- 🍎 iOS Shortcut to save links to Linkwarden.
|
- 🍎 iOS Shortcut to save Links to Linkwarden.
|
||||||
- 🔑 API keys.
|
- 🔑 API keys.
|
||||||
- ✅ Bulk actions.
|
- ✅ Bulk actions.
|
||||||
- 👥 User administration.
|
- 👥 User administration.
|
||||||
- 🌐 Support for Other Languages (i18n).
|
- 🌐 Support for Other Languages (i18n).
|
||||||
- 📁 Image and PDF Uploads.
|
- 📁 Image and PDF Uploads.
|
||||||
|
- 🎨 Custom Icons for Links and Collections.
|
||||||
|
- ⚙️ Customizable View and Adjustable Columns.
|
||||||
- ✨ And many more features. (Literally!)
|
- ✨ And many more features. (Literally!)
|
||||||
|
|
||||||
## Like what we're doing? Give us a Star ⭐
|
## Like what we're doing? Give us a Star ⭐
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const InstallApp = (props: Props) => {
|
|||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
|
|
||||||
return isOpen && !isPWA() ? (
|
return isOpen && !isPWA() ? (
|
||||||
<div className="fixed left-0 right-0 bottom-10 w-full">
|
<div className="fixed left-0 right-0 bottom-10 w-full px-5">
|
||||||
<div className="mx-auto w-fit p-2 flex justify-between gap-2 items-center border border-neutral-content rounded-xl bg-base-300 backdrop-blur-md bg-opacity-80 max-w-md">
|
<div className="mx-auto w-fit p-2 flex justify-between gap-2 items-center border border-neutral-content rounded-xl bg-base-300 backdrop-blur-md bg-opacity-80 max-w-md">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -21,6 +21,19 @@ type LinksAndCollectionAndOwner = Link & {
|
|||||||
const BROWSER_TIMEOUT = Number(process.env.BROWSER_TIMEOUT) || 5;
|
const BROWSER_TIMEOUT = Number(process.env.BROWSER_TIMEOUT) || 5;
|
||||||
|
|
||||||
export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||||
|
if (process.env.DISABLE_PRESERVATION === "true")
|
||||||
|
return await prisma.link.update({
|
||||||
|
where: { id: link.id },
|
||||||
|
data: {
|
||||||
|
lastPreserved: new Date().toISOString(),
|
||||||
|
readable: "unavailable",
|
||||||
|
image: "unavailable",
|
||||||
|
monolith: "unavailable",
|
||||||
|
pdf: "unavailable",
|
||||||
|
preview: "unavailable",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
@@ -44,7 +57,8 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (process.env.PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH) {
|
if (process.env.PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH) {
|
||||||
browserOptions.executablePath = process.env.PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH;
|
browserOptions.executablePath =
|
||||||
|
process.env.PLAYWRIGHT_LAUNCH_OPTIONS_EXECUTABLE_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
const browser = await chromium.launch(browserOptions);
|
const browser = await chromium.launch(browserOptions);
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default async function postLink(
|
|||||||
let linkType = "url";
|
let linkType = "url";
|
||||||
let imageExtension = "png";
|
let imageExtension = "png";
|
||||||
|
|
||||||
if (!link.url) linkType = link.type;
|
if (!link.url) linkType = link.type || "url";
|
||||||
else if (contentType === "application/pdf") linkType = "pdf";
|
else if (contentType === "application/pdf") linkType = "pdf";
|
||||||
else if (contentType?.startsWith("image")) {
|
else if (contentType?.startsWith("image")) {
|
||||||
linkType = "image";
|
linkType = "image";
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default async function importFromLinkwarden(
|
|||||||
url: link.url?.trim().slice(0, 254),
|
url: link.url?.trim().slice(0, 254),
|
||||||
name: link.name?.trim().slice(0, 254),
|
name: link.name?.trim().slice(0, 254),
|
||||||
description: link.description?.trim().slice(0, 254),
|
description: link.description?.trim().slice(0, 254),
|
||||||
|
importDate: new Date(link.importDate || link.createdAt),
|
||||||
collection: {
|
collection: {
|
||||||
connect: {
|
connect: {
|
||||||
id: newCollection.id,
|
id: newCollection.id,
|
||||||
|
|||||||
@@ -184,27 +184,20 @@ export default async function deleteUserById(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (user.subscriptions?.id) {
|
if (user.subscriptions?.id) {
|
||||||
const listByEmail = await stripe.customers.list({
|
const deleted = await stripe.subscriptions.cancel(
|
||||||
email: user.email?.toLowerCase(),
|
user.subscriptions.stripeSubscriptionId,
|
||||||
expand: ["data.subscriptions"],
|
{
|
||||||
});
|
cancellation_details: {
|
||||||
|
comment: body.cancellation_details?.comment,
|
||||||
|
feedback: body.cancellation_details?.feedback,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (listByEmail.data[0].subscriptions?.data[0].id) {
|
return {
|
||||||
const deleted = await stripe.subscriptions.cancel(
|
response: deleted,
|
||||||
listByEmail.data[0].subscriptions?.data[0].id,
|
status: 200,
|
||||||
{
|
};
|
||||||
cancellation_details: {
|
|
||||||
comment: body.cancellation_details?.comment,
|
|
||||||
feedback: body.cancellation_details?.feedback,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
response: deleted,
|
|
||||||
status: 200,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (user.parentSubscription?.id && user && user.emailVerified) {
|
} else if (user.parentSubscription?.id && user && user.emailVerified) {
|
||||||
await updateSeats(
|
await updateSeats(
|
||||||
user.parentSubscription.stripeSubscriptionId,
|
user.parentSubscription.stripeSubscriptionId,
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ type Data = {
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
periodStart: number;
|
periodStart: number;
|
||||||
periodEnd: number;
|
periodEnd: number;
|
||||||
|
action:
|
||||||
|
| "customer.subscription.created"
|
||||||
|
| "customer.subscription.updated"
|
||||||
|
| "customer.subscription.deleted";
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function handleSubscription({
|
export default async function handleSubscription({
|
||||||
@@ -15,6 +19,7 @@ export default async function handleSubscription({
|
|||||||
quantity,
|
quantity,
|
||||||
periodStart,
|
periodStart,
|
||||||
periodEnd,
|
periodEnd,
|
||||||
|
action,
|
||||||
}: Data) {
|
}: Data) {
|
||||||
const subscription = await prisma.subscription.findUnique({
|
const subscription = await prisma.subscription.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@@ -57,7 +62,13 @@ export default async function handleSubscription({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) throw new Error("User not found");
|
if (!user) {
|
||||||
|
if (action === "customer.subscription.deleted") {
|
||||||
|
return "User not found or deleted";
|
||||||
|
} else {
|
||||||
|
throw new Error("User not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const userId = user.id;
|
const userId = user.id;
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const PostUserSchema = () => {
|
|||||||
password: z.string().min(8).max(2048).optional(),
|
password: z.string().min(8).max(2048).optional(),
|
||||||
email: emailEnabled
|
email: emailEnabled
|
||||||
? z.string().trim().email().toLowerCase()
|
? z.string().trim().email().toLowerCase()
|
||||||
: z.string().optional(),
|
: z.string().nullish(),
|
||||||
username: emailEnabled
|
username: emailEnabled
|
||||||
? z.string().optional()
|
? z.string().optional()
|
||||||
: z
|
: z
|
||||||
@@ -59,7 +59,7 @@ export const UpdateUserSchema = () => {
|
|||||||
name: z.string().trim().min(1).max(50).optional(),
|
name: z.string().trim().min(1).max(50).optional(),
|
||||||
email: emailEnabled
|
email: emailEnabled
|
||||||
? z.string().trim().email().toLowerCase()
|
? z.string().trim().email().toLowerCase()
|
||||||
: z.string().optional(),
|
: z.string().nullish(),
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -92,7 +92,7 @@ export const PostSessionSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const PostLinkSchema = z.object({
|
export const PostLinkSchema = z.object({
|
||||||
type: z.enum(["url", "pdf", "image"]),
|
type: z.enum(["url", "pdf", "image"]).nullish(),
|
||||||
url: z.string().trim().max(2048).url().optional(),
|
url: z.string().trim().max(2048).url().optional(),
|
||||||
name: z.string().trim().max(2048).optional(),
|
name: z.string().trim().max(2048).optional(),
|
||||||
description: z.string().trim().max(2048).optional(),
|
description: z.string().trim().max(2048).optional(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const { i18n } = require("./next-i18next.config");
|
|||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
i18n,
|
i18n,
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
staticPageGenerationTimeout: 1000,
|
||||||
images: {
|
images: {
|
||||||
// For fetching the favicons
|
// For fetching the favicons
|
||||||
domains: ["t2.gstatic.com"],
|
domains: ["t2.gstatic.com"],
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "linkwarden",
|
"name": "linkwarden",
|
||||||
"version": "v2.8.0",
|
"version": "v2.8.3",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "https://github.com/linkwarden/linkwarden.git",
|
"repository": "https://github.com/linkwarden/linkwarden.git",
|
||||||
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export default async function webhook(
|
|||||||
quantity: data?.quantity ?? 1,
|
quantity: data?.quantity ?? 1,
|
||||||
periodStart: data.current_period_start,
|
periodStart: data.current_period_start,
|
||||||
periodEnd: data.current_period_end,
|
periodEnd: data.current_period_end,
|
||||||
|
action: "customer.subscription.created",
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ export default async function webhook(
|
|||||||
quantity: data?.quantity ?? 1,
|
quantity: data?.quantity ?? 1,
|
||||||
periodStart: data.current_period_start,
|
periodStart: data.current_period_start,
|
||||||
periodEnd: data.current_period_end,
|
periodEnd: data.current_period_end,
|
||||||
|
action: "customer.subscription.updated",
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -92,16 +94,7 @@ export default async function webhook(
|
|||||||
quantity: data?.lines?.data[0]?.quantity ?? 1,
|
quantity: data?.lines?.data[0]?.quantity ?? 1,
|
||||||
periodStart: data.current_period_start,
|
periodStart: data.current_period_start,
|
||||||
periodEnd: data.current_period_end,
|
periodEnd: data.current_period_end,
|
||||||
});
|
action: "customer.subscription.deleted",
|
||||||
break;
|
|
||||||
|
|
||||||
case "customer.subscription.cancelled":
|
|
||||||
await handleSubscription({
|
|
||||||
id: data.id,
|
|
||||||
active: !(data.current_period_end * 1000 < Date.now()),
|
|
||||||
quantity: data?.lines?.data[0]?.quantity ?? 1,
|
|
||||||
periodStart: data.current_period_start,
|
|
||||||
periodEnd: data.current_period_end,
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function Account() {
|
|||||||
|
|
||||||
const emailSchema = z.string().trim().email().toLowerCase();
|
const emailSchema = z.string().trim().email().toLowerCase();
|
||||||
const emailValidation = emailSchema.safeParse(user.email || "");
|
const emailValidation = emailSchema.safeParse(user.email || "");
|
||||||
if (!emailValidation.success) {
|
if (emailEnabled && !emailValidation.success) {
|
||||||
return toast.error(t("email_invalid"));
|
return toast.error(t("email_invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@
|
|||||||
"pwa_install_prompt": "Installiere Linkwarden auf deinem Startbildschirm für einen schnelleren Zugriff und ein besseres Erlebnis. <0>Mehr erfahren</0>",
|
"pwa_install_prompt": "Installiere Linkwarden auf deinem Startbildschirm für einen schnelleren Zugriff und ein besseres Erlebnis. <0>Mehr erfahren</0>",
|
||||||
"full_content": "Vollständiger Inhalt",
|
"full_content": "Vollständiger Inhalt",
|
||||||
"slower": "Langsamer",
|
"slower": "Langsamer",
|
||||||
"new_version_announcement": "Schau, was neu ist in <0>Linkwarden {{Version}}!</0>",
|
"new_version_announcement": "Schau, was neu ist in <0>Linkwarden {{version}}!</0>",
|
||||||
"creating": "Erstellen...",
|
"creating": "Erstellen...",
|
||||||
"upload_file": "Datei hochladen",
|
"upload_file": "Datei hochladen",
|
||||||
"file": "Datei",
|
"file": "Datei",
|
||||||
@@ -343,8 +343,8 @@
|
|||||||
"shift_key_tip": "Halte die Umschalttaste gedrückt, während du auf „Löschen“ klickst, um diese Bestätigung in Zukunft zu umgehen.",
|
"shift_key_tip": "Halte die Umschalttaste gedrückt, während du auf „Löschen“ klickst, um diese Bestätigung in Zukunft zu umgehen.",
|
||||||
"deleting_collection": "Lösche...",
|
"deleting_collection": "Lösche...",
|
||||||
"collection_deleted": "Sammlung gelöscht.",
|
"collection_deleted": "Sammlung gelöscht.",
|
||||||
"confirm_deletion_prompt": "Zur Bestätigung tippe „{{Name}}“ in das Feld unten:",
|
"confirm_deletion_prompt": "Zur Bestätigung tippe „{{name}}“ in das Feld unten:",
|
||||||
"type_name_placeholder": "Tippe „{{Name}}“ hier.",
|
"type_name_placeholder": "Tippe „{{name}}“ hier.",
|
||||||
"deletion_warning": "Wenn Du diese Sammlung löschst, wird ihr gesamter Inhalt unwiderruflich gelöscht und sie wird für jeden unzugänglich, auch für Mitglieder mit vorherigem Zugriff.",
|
"deletion_warning": "Wenn Du diese Sammlung löschst, wird ihr gesamter Inhalt unwiderruflich gelöscht und sie wird für jeden unzugänglich, auch für Mitglieder mit vorherigem Zugriff.",
|
||||||
"leave_prompt": "Klicke auf die Schaltfläche unten, um die aktuelle Sammlung zu verlassen.",
|
"leave_prompt": "Klicke auf die Schaltfläche unten, um die aktuelle Sammlung zu verlassen.",
|
||||||
"leave": "Verlassen",
|
"leave": "Verlassen",
|
||||||
|
|||||||
@@ -393,5 +393,41 @@
|
|||||||
"change_icon": "Змінити піктограму",
|
"change_icon": "Змінити піктограму",
|
||||||
"upload_preview_image": "Завантажте зображення для попереднього перегляду",
|
"upload_preview_image": "Завантажте зображення для попереднього перегляду",
|
||||||
"columns": "Стовпці",
|
"columns": "Стовпці",
|
||||||
"default": "За замовчуванням"
|
"default": "За замовчуванням",
|
||||||
|
"invalid_url_guide":"Будь ласка, введіть дійсну адресу для посилання. (Він має починатися з http/https)",
|
||||||
|
"email_invalid": "Введіть дійсну електронну адресу.",
|
||||||
|
"username_invalid_guide": "Ім'я користувача має складатися щонайменше з 3 символів, пробіли та спеціальні символи не допускаються.",
|
||||||
|
"team_management": "Управління командою",
|
||||||
|
"invite_user": "Запросити користувача",
|
||||||
|
"invite_users": "Запросіть користувачів",
|
||||||
|
"invite_user_desc": "Щоб запросити когось до своєї команди, введіть адресу електронної пошти нижче:",
|
||||||
|
"invite_user_note": "Зауважте, що після прийняття запрошення буде придбано додаткове місце, і з вашого облікового запису буде автоматично виставлено рахунок за це додавання.",
|
||||||
|
"invite_user_price": "Вартість кожного місця становить ${{price}} на місяць або ${{priceAnnual}} на рік, залежно від вашого поточного плану підписки.",
|
||||||
|
"send_invitation": "Надіслати запрошення",
|
||||||
|
"learn_more": "Дізнайтесь більше",
|
||||||
|
"invitation_desc": "{{owner}} запросив вас приєднатися до Linkwarden. \nЩоб продовжити, завершіть налаштування облікового запису.",
|
||||||
|
"invitation_accepted": "Запрошення прийнято!",
|
||||||
|
"status": "Статус",
|
||||||
|
"pending": "В очікуванні",
|
||||||
|
"active": "Активний",
|
||||||
|
"manage_seats": "Керуйте місцями",
|
||||||
|
"seats_purchased": "Придбано {{count}} місць",
|
||||||
|
"seat_purchased": "Придбано {{count}} місце",
|
||||||
|
"date_added": "Дата додавання",
|
||||||
|
"resend_invite": "Повторно надіслати запрошення",
|
||||||
|
"resend_invite_success": "Запрошення повторно надіслано!",
|
||||||
|
"remove_user": "Видалити користувача",
|
||||||
|
"continue_to_dashboard": "Перейдіть до панелі інструментів",
|
||||||
|
"confirm_user_removal_desc": "Щоб знову отримати доступ до Linkwarden, їм потрібно буде підписатися.",
|
||||||
|
"click_out_to_apply": "Натисніть зовні, щоб застосувати",
|
||||||
|
"submit": "Надіслати",
|
||||||
|
"thanks_for_feedback": "Дякуємо за ваш відгук!",
|
||||||
|
"quick_survey": "Швидке опитування",
|
||||||
|
"how_did_you_discover_linkwarden": "Як ви дізналися про Linkwarden?",
|
||||||
|
"rather_not_say": "Скоріше не скажу",
|
||||||
|
"search_engine": "Пошукова система (Google, Bing тощо)",
|
||||||
|
"reddit": "Reddit",
|
||||||
|
"lemmy": "Lemmy",
|
||||||
|
"people_recommendation": "Рекомендація (друг, родина тощо)",
|
||||||
|
"open_all_links": "Відкрити всі посилання"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user