kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'main' of github.com:usekaneo/kaneo

Andrej e15a68b9 d53164e2

+4112 -158
+7 -1
.env.sample
··· 11 11 # Authentication 12 12 AUTH_SECRET= 13 13 14 - # GitHub SSO & Integration 14 + # GitHub SSO (Sign in with GitHub — OAuth App) 15 + GITHUB_OAUTH_CLIENT_ID= 16 + GITHUB_OAUTH_CLIENT_SECRET= 17 + # Legacy fallback for SSO (used only if GITHUB_OAUTH_* are unset) 15 18 GITHUB_CLIENT_ID= 16 19 GITHUB_CLIENT_SECRET= 20 + 21 + # GitHub integration (GitHub App — repository sync, webhooks) 17 22 GITHUB_APP_ID= 18 23 GITHUB_WEBHOOK_SECRET= 19 24 GITHUB_PRIVATE_KEY= 25 + GITHUB_APP_NAME= 20 26 21 27 # SMTP Configuration 22 28 SMTP_HOST=
+2 -1
ENVIRONMENT_SETUP.md
··· 43 43 ### Optional Variables 44 44 45 45 Kaneo supports many optional configuration options including: 46 - - SSO providers (GitHub, Google, Discord, Custom OAuth/OIDC) 46 + - SSO providers (GitHub OAuth via `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET`, Google, Discord, Custom OAuth/OIDC) 47 + - GitHub repository integration (GitHub App: `GITHUB_APP_ID`, `GITHUB_PRIVATE_KEY`, `GITHUB_WEBHOOK_SECRET`, optional `GITHUB_APP_NAME`) — separate from GitHub SSO 47 48 - SMTP configuration for email 48 49 - Access control settings 49 50 - CORS configuration
+5 -2
apps/api/src/auth.ts
··· 25 25 import { publishEvent } from "./events"; 26 26 import { checkRegistrationAllowed } from "./utils/check-registration-allowed"; 27 27 import { generateDemoName } from "./utils/generate-demo-name"; 28 + import { getGithubSsoOAuthCredentials } from "./utils/github-sso-env"; 28 29 29 30 config(); 31 + 32 + const githubSso = getGithubSsoOAuthCredentials(); 30 33 31 34 const isRegistrationDisabled = process.env.DISABLE_REGISTRATION === "true"; 32 35 const isPasswordRegistrationDisabled = ··· 173 176 }, 174 177 socialProviders: { 175 178 github: { 176 - clientId: process.env.GITHUB_CLIENT_ID || "", 177 - clientSecret: process.env.GITHUB_CLIENT_SECRET || "", 179 + clientId: githubSso.clientId, 180 + clientSecret: githubSso.clientSecret, 178 181 scope: ["user:email"], 179 182 }, 180 183 google: {
+2 -3
apps/api/src/utils/get-settings.ts
··· 1 1 import { config } from "dotenv-mono"; 2 + import { isGithubSsoConfigured } from "./github-sso-env"; 2 3 3 4 config(); 4 5 ··· 14 15 Boolean(process.env.SMTP_SECURE) && 15 16 Boolean(process.env.SMTP_USER) && 16 17 Boolean(process.env.SMTP_PASSWORD), 17 - hasGithubSignIn: 18 - Boolean(process.env.GITHUB_CLIENT_ID) && 19 - Boolean(process.env.GITHUB_CLIENT_SECRET), 18 + hasGithubSignIn: isGithubSsoConfigured(), 20 19 hasGoogleSignIn: 21 20 Boolean(process.env.GOOGLE_CLIENT_ID) && 22 21 Boolean(process.env.GOOGLE_CLIENT_SECRET),
+24
apps/api/src/utils/github-sso-env.ts
··· 1 + /** 2 + * GitHub "Sign in with GitHub" uses OAuth client credentials. 3 + * Prefer `GITHUB_OAUTH_*` so GitHub App integration env vars can be set 4 + * without implicitly enabling SSO. Legacy `GITHUB_CLIENT_*` is still supported. 5 + */ 6 + export function getGithubSsoOAuthCredentials(): { 7 + clientId: string; 8 + clientSecret: string; 9 + } { 10 + const oauthId = process.env.GITHUB_OAUTH_CLIENT_ID?.trim(); 11 + const oauthSecret = process.env.GITHUB_OAUTH_CLIENT_SECRET?.trim(); 12 + if (oauthId && oauthSecret) { 13 + return { clientId: oauthId, clientSecret: oauthSecret }; 14 + } 15 + return { 16 + clientId: process.env.GITHUB_CLIENT_ID?.trim() || "", 17 + clientSecret: process.env.GITHUB_CLIENT_SECRET?.trim() || "", 18 + }; 19 + } 20 + 21 + export function isGithubSsoConfigured(): boolean { 22 + const { clientId, clientSecret } = getGithubSsoOAuthCredentials(); 23 + return Boolean(clientId && clientSecret); 24 + }
+22 -8
apps/docs/core/installation/environment-variables.mdx
··· 15 15 - [Authentication](#authentication) 16 16 - [Object Storage](#object-storage) 17 17 - [Access Control](#access-control) 18 - - [GitHub SSO & Integration](#github-sso-&-integration) 18 + - [GitHub SSO](#github-sso) 19 + - [GitHub integration](#github-integration) 19 20 - [Google SSO](#google-sso) 20 21 - [Discord SSO](#discord-sso) 21 22 - [Custom OAuth/OIDC](#custom-oauth-oidc) ··· 81 82 | `DISABLE_REGISTRATION` | Disable public user registration. When set to `true`, the sign-up option will not be shown on the sign-in page and new user creation is blocked (including via SSO). **Note:** Users with valid workspace invitations can still register even when this is enabled. | `false` | 82 83 | `DISABLE_PASSWORD_REGISTRATION` | Disable password-based account creation. When set to `true`, email/password sign-up is blocked, but social/OIDC registration remains available unless `DISABLE_REGISTRATION=true`. | `false` | 83 84 84 - ### GitHub SSO & Integration 85 + ### GitHub SSO 86 + 87 + Sign in with GitHub uses a [GitHub OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Set `GITHUB_OAUTH_CLIENT_ID` and `GITHUB_OAUTH_CLIENT_SECRET` (or rely on the legacy variables below). 85 88 86 89 Name | Description | 87 90 --- | --- | 88 - | `GITHUB_CLIENT_ID` | The GitHub client ID. 89 - | `GITHUB_CLIENT_SECRET` | The GitHub client secret. 90 - | `GITHUB_APP_ID` | The GitHub app ID. 91 - | `GITHUB_WEBHOOK_SECRET` | The GitHub webhook secret. 92 - | `GITHUB_PRIVATE_KEY` | The GitHub private key. 91 + | `GITHUB_OAUTH_CLIENT_ID` | OAuth client ID for GitHub sign-in. 92 + | `GITHUB_OAUTH_CLIENT_SECRET` | OAuth client secret for GitHub sign-in. 93 + | `GITHUB_CLIENT_ID` | Legacy fallback for OAuth client ID when `GITHUB_OAUTH_*` is not set. 94 + | `GITHUB_CLIENT_SECRET` | Legacy fallback for OAuth client secret when `GITHUB_OAUTH_*` is not set. 95 + 96 + ### GitHub integration 97 + 98 + Repository sync and webhooks use a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps). These variables do not enable GitHub sign-in; configure [GitHub SSO](#github-sso) separately if you want both. 99 + 100 + Name | Description | 101 + --- | --- | 102 + | `GITHUB_APP_ID` | The GitHub App ID. 103 + | `GITHUB_WEBHOOK_SECRET` | Secret used to verify GitHub webhook payloads. 104 + | `GITHUB_PRIVATE_KEY` | PEM private key for the GitHub App (JWT authentication). 105 + | `GITHUB_APP_NAME` | (Optional) GitHub App slug, used for installation links in the UI. 93 106 94 107 ### Google SSO 95 108 ··· 134 147 135 148 ## Key points 136 149 137 - - If you enable GitHub SSO, you need to set up the GitHub app which is used to authenticate users in the [GitHub Developer Settings](https://github.com/settings/developers). See the [GitHub SSO guide](/core/social-providers/github). 150 + - If you enable GitHub SSO, create a GitHub OAuth App in [GitHub Developer Settings](https://github.com/settings/developers) and set `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` (or the legacy `GITHUB_CLIENT_*` variables). See the [GitHub SSO guide](/core/social-providers/github). 151 + - If you use the GitHub repository integration, configure a GitHub App and set the integration variables in [GitHub integration](#github-integration). See the [GitHub integration configuration](/core/integrations/github/configuration) guide. 138 152 - If you enable Google SSO, you need to set up the Google app which is used to authenticate users in the [Google Cloud Console](https://console.cloud.google.com/). See the [Google SSO guide](/core/social-providers/google). 139 153 - If you enable Discord SSO, you need to set up the Discord application which is used to authenticate users in the [Discord Developer Portal](https://discord.com/developers/applications). See the [Discord SSO guide](/core/social-providers/discord). 140 154 - If you enable Custom OAuth/OIDC, you need to configure your identity provider with the appropriate redirect URI. See the [Custom OAuth/OIDC guide](/core/social-providers/custom-oauth).
+7 -13
apps/docs/core/integrations/github/configuration.mdx
··· 15 15 Add the following environment variables to your Kaneo deployment: 16 16 17 17 ```bash 18 - # GitHub App Configuration 18 + # GitHub App (integration only — repository sync, webhooks) 19 19 GITHUB_APP_ID=123456 20 - GITHUB_CLIENT_ID=Iv1.abc123def456 21 - GITHUB_CLIENT_SECRET=abc123def456ghi789jkl012mno345pqr678stu 22 20 GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- 23 21 MIIEpAIBAAKCAQEA... 24 22 [Full contents of your private key] ··· 26 24 -----END RSA PRIVATE KEY-----" 27 25 GITHUB_WEBHOOK_SECRET=your-webhook-secret-here 28 26 29 - # Optional: GitHub App Name for installation URLs 27 + # Optional: GitHub App slug for installation URLs in the UI 30 28 GITHUB_APP_NAME=kaneo-your-instance-name 31 29 ``` 30 + 31 + <Info> 32 + GitHub sign-in uses separate OAuth variables (`GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET`). You can run the integration without enabling GitHub SSO. See [GitHub SSO](/core/social-providers/github) and [Environment variables](/core/installation/environment-variables#github-sso). 33 + </Info> 32 34 33 35 ### Variable Reference 34 36 35 37 | Variable | Description | Required | Example | 36 38 |----------|-------------|----------|---------| 37 39 | `GITHUB_APP_ID` | Your GitHub App's ID | ✅ | `123456` | 38 - | `GITHUB_CLIENT_ID` | OAuth client ID from your app | ✅ | `Iv1.abc123def456` | 39 - | `GITHUB_CLIENT_SECRET` | OAuth client secret | ✅ | `abc123def456ghi789jkl012mno345pqr678stu` | 40 40 | `GITHUB_PRIVATE_KEY` | Full private key content (with newlines) | ✅ | `-----BEGIN RSA...` | 41 41 | `GITHUB_WEBHOOK_SECRET` | Secret for webhook verification | ✅ | `your-secret` | 42 - | `GITHUB_APP_NAME` | App name for installation URLs | ⚠️ | `kaneo-mycompany` | 42 + | `GITHUB_APP_NAME` | App slug for installation URLs in the UI | ⚠️ | `kaneo-mycompany` | 43 43 44 44 <Warning> 45 45 `GITHUB_APP_NAME` is optional but recommended. It's used to generate direct installation links in the UI. ··· 58 58 environment: 59 59 # ... other environment variables 60 60 GITHUB_APP_ID: "123456" 61 - GITHUB_CLIENT_ID: "Iv1.abc123def456" 62 - GITHUB_CLIENT_SECRET: "abc123def456ghi789jkl012mno345pqr678stu" 63 61 GITHUB_PRIVATE_KEY: | 64 62 -----BEGIN RSA PRIVATE KEY----- 65 63 MIIEpAIBAAKCAQEA... ··· 88 86 type: Opaque 89 87 stringData: 90 88 GITHUB_APP_ID: "123456" 91 - GITHUB_CLIENT_ID: "Iv1.abc123def456" 92 - GITHUB_CLIENT_SECRET: "abc123def456ghi789jkl012mno345pqr678stu" 93 89 GITHUB_PRIVATE_KEY: | 94 90 -----BEGIN RSA PRIVATE KEY----- 95 91 MIIEpAIBAAKCAQEA... ··· 125 121 ```bash 126 122 # .env file 127 123 GITHUB_APP_ID=123456 128 - GITHUB_CLIENT_ID=Iv1.abc123def456 129 - GITHUB_CLIENT_SECRET=abc123def456ghi789jkl012mno345pqr678stu 130 124 GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- 131 125 MIIEpAIBAAKCAQEA... 132 126 [Full contents of your private key]
+3 -3
apps/docs/core/integrations/github/setup.mdx
··· 137 137 From the app settings page, note down these important values: 138 138 139 139 - **App ID** (shown at the top of the page) 140 - - **Client ID** (in the "Basic information" section) 141 - - **Client Secret** (click "Generate a new client secret") 142 140 - **Webhook Secret** (the one you created earlier) 143 141 - **Private Key** (contents of the downloaded `.pem` file) 144 142 143 + The GitHub App also shows an OAuth **Client ID** and **Client Secret** under "Basic information". Kaneo’s **repository integration** does not use those environment variables; they are only relevant if you enable **Sign in with GitHub** using `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` (see [GitHub SSO](/core/social-providers/github)). 144 + 145 145 <Info> 146 - You'll need all of these values for the [configuration step](/core/integrations/github/configuration). 146 + You need **App ID**, **private key**, and **webhook secret** for the [configuration step](/core/integrations/github/configuration). 147 147 </Info> 148 148 </Step> 149 149 </Steps>
+4 -6
apps/docs/core/integrations/github/troubleshooting.mdx
··· 105 105 **Solutions**: 106 106 1. **Verify All Variables Are Set** 107 107 ```bash 108 - # Check if environment variables are loaded 108 + # Check if integration environment variables are loaded (do not print secrets) 109 109 echo $GITHUB_APP_ID 110 - echo $GITHUB_CLIENT_ID 111 - # etc. 110 + if [ -n "$GITHUB_WEBHOOK_SECRET" ]; then echo "GITHUB_WEBHOOK_SECRET is set"; else echo "GITHUB_WEBHOOK_SECRET is missing"; fi 112 111 ``` 113 112 114 113 2. **Check Private Key Format** ··· 257 256 258 257 1. **Verify Environment Variables** 259 258 ```bash 260 - # Check that all required variables are set (don't print private key!) 259 + # Check that all required integration variables are set (don't print private key or webhook secret!) 261 260 echo "App ID: $GITHUB_APP_ID" 262 - echo "Client ID: $GITHUB_CLIENT_ID" 263 - echo "Webhook Secret: [REDACTED]" 264 261 echo "App Name: $GITHUB_APP_NAME" 262 + if [ -n "$GITHUB_WEBHOOK_SECRET" ]; then echo "GITHUB_WEBHOOK_SECRET is set"; else echo "GITHUB_WEBHOOK_SECRET is missing"; fi 265 263 ``` 266 264 267 265 2. **Test Repository Access**
+2 -2
apps/docs/core/social-providers/github.mdx
··· 13 13 2. Create a new OAuth app. 14 14 3. Set the redirect URI to `KANEO_API_URL/api/auth/callback/github`. 15 15 4. Copy the client ID and client secret. 16 - 5. Set the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` environment variables. 16 + 5. Set `GITHUB_OAUTH_CLIENT_ID` and `GITHUB_OAUTH_CLIENT_SECRET` (or the legacy `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` if you prefer). 17 17 18 - For more information on what your `KANEO_API_URL` is, see the [Environment variables](/core/installation/environment-variables#github-sso--integration) page. 18 + For more information on what your `KANEO_API_URL` is, see the [Environment variables](/core/installation/environment-variables#github-sso) page. 19 19 20 20 ## Usage 21 21
+139
apps/web/src/components/activity/comment-card.test.tsx
··· 1 + import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 2 + import { fireEvent, render, screen } from "@testing-library/react"; 3 + import type { ReactElement, ReactNode } from "react"; 4 + import { cloneElement, isValidElement } from "react"; 5 + import { describe, expect, it, vi } from "vitest"; 6 + import CommentCard from "./comment-card"; 7 + 8 + vi.mock("@/components/activity/comment-editor", () => ({ 9 + default: ({ value }: { value: string }) => <div>{value}</div>, 10 + })); 11 + 12 + vi.mock("@/components/providers/auth-provider/hooks/use-auth", () => ({ 13 + useAuth: () => ({ user: null }), 14 + })); 15 + 16 + vi.mock("@/hooks/mutations/comment/use-update-comment", () => ({ 17 + default: () => ({ 18 + mutateAsync: vi.fn(), 19 + isPending: false, 20 + }), 21 + })); 22 + 23 + vi.mock("react-i18next", () => ({ 24 + useTranslation: () => ({ 25 + t: (key: string) => key, 26 + }), 27 + })); 28 + 29 + vi.mock("@/lib/format", () => ({ 30 + formatRelativeTime: () => "2 hours ago", 31 + formatDateTime: () => "Apr 5, 2026, 11:38 AM", 32 + })); 33 + 34 + vi.mock("@/components/ui/tooltip", async () => { 35 + const React = await import("react"); 36 + 37 + function Tooltip({ children }: { children: ReactNode }) { 38 + const [open, setOpen] = React.useState(false); 39 + return ( 40 + <div data-testid="tooltip-root"> 41 + {React.Children.map(children, (child) => 42 + isValidElement(child) 43 + ? cloneElement( 44 + child as ReactElement<{ 45 + open?: boolean; 46 + setOpen?: (open: boolean) => void; 47 + }>, 48 + { 49 + open, 50 + setOpen, 51 + }, 52 + ) 53 + : child, 54 + )} 55 + </div> 56 + ); 57 + } 58 + 59 + function TooltipTrigger({ 60 + children, 61 + setOpen, 62 + }: { 63 + children: ReactElement; 64 + setOpen?: (open: boolean) => void; 65 + }) { 66 + return cloneElement(children as ReactElement<Record<string, unknown>>, { 67 + onMouseEnter: () => setOpen?.(true), 68 + onMouseLeave: () => setOpen?.(false), 69 + onFocus: () => setOpen?.(true), 70 + onBlur: () => setOpen?.(false), 71 + }); 72 + } 73 + 74 + function TooltipContent({ 75 + children, 76 + open, 77 + }: { 78 + children: ReactNode; 79 + open?: boolean; 80 + }) { 81 + return open ? <div role="tooltip">{children}</div> : null; 82 + } 83 + 84 + function TooltipProvider({ children }: { children: ReactNode }) { 85 + return <>{children}</>; 86 + } 87 + 88 + return { 89 + Tooltip, 90 + TooltipContent, 91 + TooltipProvider, 92 + TooltipTrigger, 93 + }; 94 + }); 95 + 96 + function renderCommentCard() { 97 + const queryClient = new QueryClient({ 98 + defaultOptions: { 99 + queries: { retry: false }, 100 + mutations: { retry: false }, 101 + }, 102 + }); 103 + 104 + return render( 105 + <QueryClientProvider client={queryClient}> 106 + <CommentCard 107 + commentId="comment-1" 108 + taskId="task-1" 109 + content="Test comment" 110 + createdAt="2026-04-05T09:38:50.000Z" 111 + user={{ 112 + id: "user-1", 113 + name: "Tin", 114 + email: "tin@example.com", 115 + image: null, 116 + }} 117 + /> 118 + </QueryClientProvider>, 119 + ); 120 + } 121 + 122 + describe("CommentCard", () => { 123 + it("shows full date+short time in tooltip on hover/focus", async () => { 124 + renderCommentCard(); 125 + 126 + const trigger = screen.getByRole("button", { 127 + name: "Apr 5, 2026, 11:38 AM", 128 + }); 129 + 130 + fireEvent.mouseEnter(trigger); 131 + expect(await screen.findByText("Apr 5, 2026, 11:38 AM")).toBeVisible(); 132 + 133 + fireEvent.mouseLeave(trigger); 134 + expect(screen.queryByText("Apr 5, 2026, 11:38 AM")).not.toBeInTheDocument(); 135 + 136 + fireEvent.focus(trigger); 137 + expect(await screen.findByText("Apr 5, 2026, 11:38 AM")).toBeVisible(); 138 + }); 139 + });
+129 -114
apps/web/src/components/activity/comment-card.tsx
··· 18 18 TooltipTrigger, 19 19 } from "@/components/ui/tooltip"; 20 20 import useUpdateComment from "@/hooks/mutations/comment/use-update-comment"; 21 - import { formatRelativeTime } from "@/lib/format"; 21 + import { formatDateTime, formatRelativeTime } from "@/lib/format"; 22 22 import { toast } from "@/lib/toast"; 23 23 24 24 type CommentCardProps = { ··· 57 57 const githubProfileUrl = 58 58 isFromGitHub && user?.name ? `https://github.com/${user.name}` : null; 59 59 const commentUrl = externalUrl || null; 60 + const fullTimestamp = formatDateTime(createdAt); 60 61 61 62 const handleEdit = useCallback(() => { 62 63 setEditedContent(content); ··· 103 104 ]); 104 105 105 106 return ( 106 - <div className="group relative w-full rounded-xl border border-border/80 bg-card/60"> 107 - <div className="flex items-center gap-2 px-3 pt-2.5"> 108 - <HoverCard> 109 - <HoverCardTrigger> 110 - <div className="flex cursor-pointer items-center gap-2"> 111 - <Avatar className="h-6 w-6"> 112 - <AvatarImage src={user?.image ?? ""} alt={user?.name || ""} /> 113 - <AvatarFallback className="bg-muted text-xs font-medium"> 114 - {user?.name?.charAt(0).toUpperCase()} 115 - </AvatarFallback> 116 - </Avatar> 117 - <span className="text-sm font-medium text-foreground/92 hover:text-foreground transition-colors"> 118 - {user?.name} 119 - </span> 120 - </div> 121 - </HoverCardTrigger> 122 - <HoverCardContent className="w-64 p-3"> 123 - <div className="flex items-center gap-3"> 124 - <Avatar className="h-10 w-10"> 125 - <AvatarImage src={user?.image ?? ""} alt={user?.name || ""} /> 126 - <AvatarFallback className="bg-muted text-xs font-medium"> 127 - {user?.name?.charAt(0).toUpperCase()} 128 - </AvatarFallback> 129 - </Avatar> 130 - <div className="min-w-0 flex-1"> 131 - <p className="text-sm font-medium text-foreground leading-none"> 107 + <TooltipProvider> 108 + <div className="group relative w-full rounded-xl border border-border/80 bg-card/60"> 109 + <div className="flex items-center gap-2 px-3 pt-2.5"> 110 + <HoverCard> 111 + <HoverCardTrigger> 112 + <div className="flex cursor-pointer items-center gap-2"> 113 + <Avatar className="h-6 w-6"> 114 + <AvatarImage src={user?.image ?? ""} alt={user?.name || ""} /> 115 + <AvatarFallback className="bg-muted text-xs font-medium"> 116 + {user?.name?.charAt(0).toUpperCase()} 117 + </AvatarFallback> 118 + </Avatar> 119 + <span className="text-sm font-medium text-foreground/92 hover:text-foreground transition-colors"> 132 120 {user?.name} 133 - </p> 134 - {user?.email && ( 135 - <p className="mt-1 text-xs text-muted-foreground"> 136 - {user.email} 121 + </span> 122 + </div> 123 + </HoverCardTrigger> 124 + <HoverCardContent className="w-64 p-3"> 125 + <div className="flex items-center gap-3"> 126 + <Avatar className="h-10 w-10"> 127 + <AvatarImage src={user?.image ?? ""} alt={user?.name || ""} /> 128 + <AvatarFallback className="bg-muted text-xs font-medium"> 129 + {user?.name?.charAt(0).toUpperCase()} 130 + </AvatarFallback> 131 + </Avatar> 132 + <div className="min-w-0 flex-1"> 133 + <p className="text-sm font-medium text-foreground leading-none"> 134 + {user?.name} 137 135 </p> 138 - )} 139 - {isFromGitHub && ( 140 - <div className="mt-1.5 flex items-center gap-1"> 141 - <Github className="size-3 text-muted-foreground" /> 142 - <span className="text-xs text-muted-foreground"> 143 - {t("activity:comment.github")} 144 - </span> 145 - </div> 146 - )} 136 + {user?.email && ( 137 + <p className="mt-1 text-xs text-muted-foreground"> 138 + {user.email} 139 + </p> 140 + )} 141 + {isFromGitHub && ( 142 + <div className="mt-1.5 flex items-center gap-1"> 143 + <Github className="size-3 text-muted-foreground" /> 144 + <span className="text-xs text-muted-foreground"> 145 + {t("activity:comment.github")} 146 + </span> 147 + </div> 148 + )} 149 + </div> 147 150 </div> 148 - </div> 149 - {githubProfileUrl && ( 151 + {githubProfileUrl && ( 152 + <a 153 + href={githubProfileUrl} 154 + target="_blank" 155 + rel="noopener noreferrer" 156 + className="mt-3 flex items-center gap-1.5 border-t border-border pt-3 text-xs text-muted-foreground transition-colors hover:text-foreground" 157 + > 158 + <ExternalLink className="size-3" /> 159 + {t("activity:comment.viewGithubProfile")} 160 + </a> 161 + )} 162 + </HoverCardContent> 163 + </HoverCard> 164 + 165 + <Tooltip> 166 + <TooltipTrigger asChild> 167 + <button 168 + type="button" 169 + className="cursor-default text-xs text-muted-foreground/62 outline-none focus-visible:ring-2 focus-visible:ring-ring/70 focus-visible:ring-offset-2 focus-visible:ring-offset-background" 170 + aria-label={fullTimestamp} 171 + title={fullTimestamp} 172 + > 173 + <time dateTime={createdAt}> 174 + {formatRelativeTime(createdAt)} 175 + </time> 176 + </button> 177 + </TooltipTrigger> 178 + <TooltipContent> 179 + <p className="text-xs">{fullTimestamp}</p> 180 + </TooltipContent> 181 + </Tooltip> 182 + 183 + {commentUrl && ( 184 + <> 185 + <span className="text-xs text-muted-foreground/40">·</span> 150 186 <a 151 - href={githubProfileUrl} 187 + href={commentUrl} 152 188 target="_blank" 153 189 rel="noopener noreferrer" 154 - className="mt-3 flex items-center gap-1.5 border-t border-border pt-3 text-xs text-muted-foreground transition-colors hover:text-foreground" 190 + className="flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground" 155 191 > 156 - <ExternalLink className="size-3" /> 157 - {t("activity:comment.viewGithubProfile")} 192 + <Github className="size-3" /> 193 + {t("activity:comment.commentedOnGithub")} 158 194 </a> 159 - )} 160 - </HoverCardContent> 161 - </HoverCard> 195 + </> 196 + )} 197 + </div> 162 198 163 - <span className="text-xs text-muted-foreground/62"> 164 - {formatRelativeTime(createdAt)} 165 - </span> 166 - 167 - {commentUrl && ( 168 - <> 169 - <span className="text-xs text-muted-foreground/40">·</span> 170 - <a 171 - href={commentUrl} 172 - target="_blank" 173 - rel="noopener noreferrer" 174 - className="flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground" 175 - > 176 - <Github className="size-3" /> 177 - {t("activity:comment.commentedOnGithub")} 178 - </a> 179 - </> 180 - )} 181 - </div> 182 - 183 - {canEdit && !isEditing && ( 184 - <TooltipProvider> 199 + {canEdit && !isEditing && ( 185 200 <Tooltip> 186 201 <TooltipTrigger asChild> 187 202 <Button ··· 197 212 <p className="text-xs">{t("activity:comment.edit")}</p> 198 213 </TooltipContent> 199 214 </Tooltip> 200 - </TooltipProvider> 201 - )} 215 + )} 202 216 203 - <div className="pt-0.5"> 204 - <CommentEditor 205 - value={isEditing ? editedContent : content} 206 - onChange={isEditing ? setEditedContent : undefined} 207 - placeholder={t("activity:comment.editPlaceholder")} 208 - taskId={taskId} 209 - uploadSurface="comment" 210 - className={ 211 - isEditing 212 - ? "[&_.kaneo-comment-editor-content_.ProseMirror]:min-h-[3rem] [&_.kaneo-comment-editor-content_.ProseMirror]:max-h-none [&_.kaneo-comment-editor-content_.ProseMirror]:overflow-visible [&_.kaneo-comment-editor-content_.ProseMirror]:px-3 [&_.kaneo-comment-editor-content_.ProseMirror]:pt-2.5 [&_.kaneo-comment-editor-content_.ProseMirror]:pb-2" 213 - : "kaneo-comment-viewer [&_.kaneo-comment-editor-content_.ProseMirror]:px-3 [&_.kaneo-comment-editor-content_.ProseMirror]:pt-2 [&_.kaneo-comment-editor-content_.ProseMirror]:pb-3" 214 - } 215 - autoFocus={isEditing} 216 - readOnly={!isEditing} 217 - onSubmitShortcut={isEditing ? handleSave : undefined} 218 - onCancelShortcut={isEditing ? handleCancel : undefined} 219 - /> 217 + <div className="pt-0.5"> 218 + <CommentEditor 219 + value={isEditing ? editedContent : content} 220 + onChange={isEditing ? setEditedContent : undefined} 221 + placeholder={t("activity:comment.editPlaceholder")} 222 + taskId={taskId} 223 + uploadSurface="comment" 224 + className={ 225 + isEditing 226 + ? "[&_.kaneo-comment-editor-content_.ProseMirror]:min-h-[3rem] [&_.kaneo-comment-editor-content_.ProseMirror]:max-h-none [&_.kaneo-comment-editor-content_.ProseMirror]:overflow-visible [&_.kaneo-comment-editor-content_.ProseMirror]:px-3 [&_.kaneo-comment-editor-content_.ProseMirror]:pt-2.5 [&_.kaneo-comment-editor-content_.ProseMirror]:pb-2" 227 + : "kaneo-comment-viewer [&_.kaneo-comment-editor-content_.ProseMirror]:px-3 [&_.kaneo-comment-editor-content_.ProseMirror]:pt-2 [&_.kaneo-comment-editor-content_.ProseMirror]:pb-3" 228 + } 229 + autoFocus={isEditing} 230 + readOnly={!isEditing} 231 + onSubmitShortcut={isEditing ? handleSave : undefined} 232 + onCancelShortcut={isEditing ? handleCancel : undefined} 233 + /> 234 + </div> 235 + 236 + {isEditing && ( 237 + <div className="flex items-center justify-end gap-2 border-border/70 border-t bg-card/60 px-3 py-2"> 238 + <Button 239 + variant="secondary" 240 + size="sm" 241 + onClick={handleCancel} 242 + disabled={isPending} 243 + className="h-7 px-2.5 text-xs" 244 + > 245 + {t("common:actions.cancel")} 246 + </Button> 247 + <Button 248 + variant="default" 249 + size="sm" 250 + onClick={handleSave} 251 + disabled={isPending || !editedContent.trim()} 252 + className="h-7 px-2.5 text-xs" 253 + > 254 + {t("activity:comment.save")} 255 + </Button> 256 + </div> 257 + )} 220 258 </div> 221 - 222 - {isEditing && ( 223 - <div className="flex items-center justify-end gap-2 border-border/70 border-t bg-card/60 px-3 py-2"> 224 - <Button 225 - variant="secondary" 226 - size="sm" 227 - onClick={handleCancel} 228 - disabled={isPending} 229 - className="h-7 px-2.5 text-xs" 230 - > 231 - {t("common:actions.cancel")} 232 - </Button> 233 - <Button 234 - variant="default" 235 - size="sm" 236 - onClick={handleSave} 237 - disabled={isPending || !editedContent.trim()} 238 - className="h-7 px-2.5 text-xs" 239 - > 240 - {t("activity:comment.save")} 241 - </Button> 242 - </div> 243 - )} 244 - </div> 259 + </TooltipProvider> 245 260 ); 246 261 }
+9 -4
apps/web/src/components/activity/comment-editor.tsx
··· 187 187 const taskIdRef = useRef(taskId); 188 188 const ensureTaskIdRef = useRef(ensureTaskId); 189 189 const uploadSurfaceRef = useRef(uploadSurface); 190 + const onSubmitShortcutRef = useRef(onSubmitShortcut); 191 + const onCancelShortcutRef = useRef(onCancelShortcut); 192 + onSubmitShortcutRef.current = onSubmitShortcut; 193 + onCancelShortcutRef.current = onCancelShortcut; 190 194 const pendingImageInsertRef = useRef<{ 191 195 editor: Editor; 192 196 range?: SlashRange; ··· 809 813 } 810 814 811 815 if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { 812 - if (!readOnly && !disabled && onSubmitShortcut) { 816 + const submit = onSubmitShortcutRef.current; 817 + if (!readOnly && !disabled && submit) { 813 818 event.preventDefault(); 814 - onSubmitShortcut(); 819 + submit(); 815 820 return true; 816 821 } 817 822 } ··· 820 825 event.key === "Escape" && 821 826 !readOnly && 822 827 !disabled && 823 - onCancelShortcut 828 + onCancelShortcutRef.current 824 829 ) { 825 830 event.preventDefault(); 826 - onCancelShortcut(); 831 + onCancelShortcutRef.current(); 827 832 return true; 828 833 } 829 834
+12
apps/web/src/lib/format.ts
··· 43 43 ); 44 44 } 45 45 46 + /** Locale-aware full date and time (for tooltips next to relative labels like "yesterday"). */ 47 + export function formatDateTime(value: DateInput, locale?: string) { 48 + return formatDate( 49 + value, 50 + { 51 + dateStyle: "medium", 52 + timeStyle: "short", 53 + }, 54 + locale, 55 + ); 56 + } 57 + 46 58 export function formatRelativeTime( 47 59 value: DateInput, 48 60 locale?: string,
+26 -1
apps/web/src/routes/_layout/_authenticated/dashboard/settings/projects/$projectId/general.tsx
··· 230 230 ], 231 231 ); 232 232 233 + const saveProjectRef = useRef(saveProject); 234 + const projectFormRef = useRef(projectForm); 235 + saveProjectRef.current = saveProject; 236 + projectFormRef.current = projectForm; 237 + 233 238 const debouncedSave = useCallback(() => { 234 239 if (debounceTimeoutRef.current) { 235 240 clearTimeout(debounceTimeoutRef.current); ··· 246 251 }, [projectForm, saveProject]); 247 252 248 253 useEffect(() => { 254 + // Do not gate on formState.isDirty here: after setValue (e.g. icon pick), the 255 + // watch callback can run before RHF updates isDirty, so the debounced save never runs. 249 256 const subscription = projectForm.watch(() => { 250 - if (!projectForm.formState.isDirty) return; 251 257 debouncedSave(); 252 258 }); 253 259 ··· 258 264 return () => { 259 265 if (debounceTimeoutRef.current) { 260 266 clearTimeout(debounceTimeoutRef.current); 267 + debounceTimeoutRef.current = null; 261 268 } 269 + // Flush pending edits if the user navigates away before the debounce fires. 270 + void (async () => { 271 + const latest = projectFormRef.current.getValues() as ProjectFormValues; 272 + const normalized = normalizeProjectValues(latest); 273 + const last = lastSavedRef.current; 274 + const hasPendingChanges = 275 + !last || 276 + last.name !== normalized.name || 277 + last.slug !== normalized.slug || 278 + last.description !== normalized.description || 279 + last.icon !== normalized.icon; 280 + if (!hasPendingChanges) return; 281 + 282 + const isValid = await projectFormRef.current.trigger(); 283 + if (isValid) { 284 + await saveProjectRef.current(latest); 285 + } 286 + })(); 262 287 }; 263 288 }, []); 264 289
+6
i18n/resources.ts
··· 5 5 import frFR from "./fr-FR.json"; 6 6 import mkMK from "./mk-MK.json"; 7 7 import nlNL from "./nl-NL.json"; 8 + import ruRU from "./ru-RU.json"; 9 + import ukUA from "./uk-UA.json"; 8 10 9 11 export const supportedLocales = [ 10 12 "mk-MK", ··· 14 16 "en-US", 15 17 "es-ES", 16 18 "fr-FR", 19 + "ru-RU", 20 + "uk-UA", 17 21 ] as const; 18 22 19 23 export type AppLocale = (typeof supportedLocales)[number]; ··· 28 32 "el-GR": elGR, 29 33 "fr-FR": frFR, 30 34 "es-ES": esES, 35 + "ru-RU": ruRU, 36 + "uk-UA": ukUA, 31 37 } as const;
+1839
i18n/ru-RU.json
··· 1 + { 2 + "common": { 3 + "appName": "Kaneo", 4 + "actions": { 5 + "cancel": "Отмена", 6 + "close": "Закрыть", 7 + "clearAll": "Очистить все", 8 + "delete": "Удалить", 9 + "deleting": "Удаление...", 10 + "markAllRead": "Отметить все как прочитанные", 11 + "remove": "Убрать", 12 + "reset": "Сбросить", 13 + "filter": "Фильтр", 14 + "clearAllFilters": "Сбросить все фильтры" 15 + }, 16 + "a11y": { 17 + "toggleSidebar": "Переключить боковую панель" 18 + }, 19 + "sidebar": { 20 + "title": "Боковая панель", 21 + "mobileDescription": "Отображает мобильную боковую панель." 22 + }, 23 + "empty": { 24 + "loading": "Загрузка..." 25 + }, 26 + "pagination": { 27 + "label": "Пагинация", 28 + "previous": "Назад", 29 + "next": "Вперёд", 30 + "previousPage": "Перейти на предыдущую страницу", 31 + "nextPage": "Перейти на следующую страницу", 32 + "morePages": "Ещё страницы" 33 + }, 34 + "breadcrumb": { 35 + "label": "Навигационная цепочка", 36 + "more": "Ещё" 37 + }, 38 + "language": { 39 + "english": "Английский", 40 + "german": "Немецкий", 41 + "greek": "Греческий", 42 + "macedonian": "Македонский", 43 + "french": "Французский", 44 + "spanish": "Испанский", 45 + "dutch": "Нидерландский" 46 + }, 47 + "people": { 48 + "someone": "Кто-то", 49 + "unknown": "Неизвестно" 50 + }, 51 + "error": { 52 + "title": "Что-то пошло не так", 53 + "troubleshooting": "Шаги для устранения неполадок:", 54 + "tryAgain": "Попробовать снова", 55 + "viewDeploymentGuide": "Руководство по развёртыванию", 56 + "refreshPage": "Обновить страницу" 57 + }, 58 + "formats": { 59 + "never": "Никогда" 60 + }, 61 + "modals": { 62 + "createProject": { 63 + "title": "Создать новый проект", 64 + "breadcrumbNew": "Создать новый проект", 65 + "workspaceFallback": "РАБОЧАЯ ОБЛАСТЬ", 66 + "description": "Создайте новый проект в вашей рабочей области, указав название, ключ и выбрав иконку.", 67 + "pickIcon": "Выбрать иконку", 68 + "searchIcons": "Поиск иконок...", 69 + "projectName": "Название проекта", 70 + "keyLabel": "Ключ:", 71 + "keyHint": "Используется для идентификаторов задач (например, {{example}}-123)", 72 + "createButton": "Создать проект", 73 + "successToast": "Проект успешно создан", 74 + "errorToast": "Не удалось создать проект" 75 + }, 76 + "createWorkspace": { 77 + "breadcrumbKaneo": "KANEO", 78 + "title": "Создать новую рабочую область", 79 + "description": "Создайте новую рабочую область, указав её название.", 80 + "namePlaceholder": "Название рабочей области", 81 + "descriptionPlaceholder": "Добавить описание...", 82 + "createButton": "Создать рабочую область", 83 + "successToast": "Рабочая область успешно создана", 84 + "errorToast": "Не удалось создать рабочую область" 85 + }, 86 + "createTask": { 87 + "breadcrumbTask": "ЗАДАЧА", 88 + "title": "Новая задача", 89 + "description": "Создайте новую задачу, указав название, описание и другие детали.", 90 + "taskTitlePlaceholder": "Название задачи", 91 + "descriptionPlaceholder": "Добавьте описание задачи...", 92 + "chooseProjectForImages": "Выберите проект перед загрузкой изображений.", 93 + "prepareTaskError": "Не удалось подготовить задачу", 94 + "successCreated": "Задача успешно создана", 95 + "successUpdated": "Задача успешно обновлена", 96 + "createError": "Не удалось создать задачу", 97 + "priority": "Приоритет", 98 + "statusFallback": "В работе", 99 + "startDate": "Дата начала", 100 + "dueDate": "Срок выполнения", 101 + "clearStartDate": "Очистить дату начала", 102 + "clearDueDate": "Очистить срок выполнения", 103 + "assign": "Назначить", 104 + "assignUnassigned": "Не назначено", 105 + "assignUnassignedTitle": "Не назначено", 106 + "labels": "Метки", 107 + "searchLabels": "Поиск меток...", 108 + "noLabelsFound": "Метки не найдены", 109 + "createLabel": "Создать «{{name}}»", 110 + "chooseColor": "Выберите цвет", 111 + "labelCreated": "Метка создана", 112 + "labelCreateError": "Не удалось создать метку", 113 + "createMore": "Создать ещё", 114 + "createButton": "Создать задачу", 115 + "untitledTask": "Задача без названия", 116 + "labelColors": { 117 + "stone": "Камень", 118 + "slate": "Сланец", 119 + "lavender": "Лаванда", 120 + "sage": "Шалфей", 121 + "forest": "Лес", 122 + "amber": "Янтарь", 123 + "terracotta": "Терракота", 124 + "rose": "Роза", 125 + "crimson": "Малиновый" 126 + } 127 + } 128 + } 129 + }, 130 + "auth": { 131 + "signIn": { 132 + "pageTitle": "Вход", 133 + "title": "С возвращением", 134 + "subtitle": "Введите свои учётные данные для доступа к рабочей области", 135 + "invitationSubtitle": "Войдите, чтобы принять приглашение", 136 + "invitationAlert": "После входа вы сможете принять приглашение в рабочую область.", 137 + "signingIn": "Вход...", 138 + "continueWithGoogle": "Продолжить с Google", 139 + "continueWithGithub": "Продолжить с GitHub", 140 + "continueWithDiscord": "Продолжить с Discord", 141 + "continueWithOidc": "Продолжить с OIDC", 142 + "lastUsed": "Использовался последним", 143 + "registrationDisabled": "Публичная регистрация отключена. Используйте приглашение для создания аккаунта.", 144 + "passwordRegistrationDisabled": "Регистрация по паролю отключена. Используйте настроенный метод входа через социальные сети или OIDC для создания аккаунта.", 145 + "toggleMessage": "Нет аккаунта?", 146 + "toggleLink": "Создать аккаунт", 147 + "guestSuccess": "Вход выполнен как гость", 148 + "guestError": "Не удалось войти как гость", 149 + "oidcError": "Не удалось войти через OIDC", 150 + "googleError": "Не удалось войти через Google", 151 + "githubError": "Не удалось войти через GitHub", 152 + "discordError": "Не удалось войти через Discord" 153 + }, 154 + "providers": { 155 + "google": "Google", 156 + "discord": "Discord" 157 + }, 158 + "forms": { 159 + "or": "или", 160 + "email": "Электронная почта", 161 + "password": "Пароль", 162 + "emailPlaceholder": "me@example.com", 163 + "passwordPlaceholder": "••••••••", 164 + "showPassword": "Показать пароль", 165 + "hidePassword": "Скрыть пароль" 166 + }, 167 + "checkEmail": { 168 + "pageTitle": "Проверьте почту", 169 + "title": "Проверьте свою почту", 170 + "inboxMessage": "Мы отправили вам временную ссылку для входа. Пожалуйста, проверьте свой почтовый ящик по адресу <email>{{email}}</email>.", 171 + "emailFallback": "ваш адрес электронной почты", 172 + "backToLogin": "Вернуться к входу" 173 + }, 174 + "signUp": { 175 + "pageTitle": "Создание аккаунта", 176 + "title": "Создать аккаунт", 177 + "subtitleInvitation": "Создайте аккаунт, чтобы принять приглашение", 178 + "subtitleRegistrationDisabled": "Для регистрации требуется приглашение", 179 + "subtitlePasswordDisabled": "Используйте вход через социальные сети или OIDC для создания аккаунта", 180 + "subtitleDefault": "Начните работу с рабочей областью", 181 + "invitationAlert": "После создания аккаунта вы сможете принять приглашение в рабочую область.", 182 + "registrationDisabledAlert": "Регистрация в данный момент отключена. Если вы получили приглашение, введите адрес электронной почты, на который оно было отправлено.", 183 + "passwordDisabledAlert": "Создание аккаунта по паролю отключено. Используйте настроенный метод входа через социальные сети или OIDC на странице входа.", 184 + "signingIn": "Вход...", 185 + "continueAsGuest": "Продолжить как гость", 186 + "toggleMessage": "Уже есть аккаунт?", 187 + "toggleLink": "Войти" 188 + }, 189 + "verifyOtp": { 190 + "pageTitle": "Проверка кода", 191 + "title": "Введите код подтверждения", 192 + "subtitle": "Используйте 6-значный код, отправленный на вашу почту", 193 + "codeSentTo": "Код отправлен на {{email}}", 194 + "verificationCodeLabel": "Код подтверждения", 195 + "verifying": "Проверка...", 196 + "verifyAndSignIn": "Подтвердить и войти", 197 + "changeEmail": "Изменить адрес", 198 + "resend": "Отправить повторно", 199 + "validation": { 200 + "codeLength": "Код должен содержать 6 цифр" 201 + }, 202 + "toast": { 203 + "invalidCode": "Неверный код подтверждения", 204 + "signedInSuccess": "Вход выполнен успешно!", 205 + "verifyFailed": "Не удалось проверить код", 206 + "resendFailed": "Не удалось отправить код повторно", 207 + "resendSuccess": "Новый код подтверждения отправлен!" 208 + } 209 + }, 210 + "otpSignIn": { 211 + "sendFailed": "Не удалось отправить код подтверждения", 212 + "codeSent": "Код подтверждения отправлен! Проверьте почту.", 213 + "sending": "Отправка...", 214 + "sendVerificationCode": "Отправить код подтверждения" 215 + }, 216 + "signInForm": { 217 + "failedSignIn": "Не удалось войти", 218 + "signedInSuccess": "Вход выполнен успешно", 219 + "signingIn": "Вход...", 220 + "signIn": "Войти" 221 + }, 222 + "signUpForm": { 223 + "fullName": "Полное имя", 224 + "namePlaceholder": "Иван Иванов", 225 + "failedSignUp": "Не удалось зарегистрироваться", 226 + "accountCreated": "Аккаунт успешно создан", 227 + "passwordTooShort": "Пароль слишком короткий", 228 + "creatingAccount": "Создание аккаунта...", 229 + "createAccount": "Создать аккаунт" 230 + }, 231 + "invitation": { 232 + "pageTitleAccept": "Принять приглашение", 233 + "pageTitleError": "Ошибка приглашения", 234 + "pageTitleInvalid": "Недействительное приглашение", 235 + "loadingTitle": "Загрузка приглашения...", 236 + "errorTitle": "Ошибка приглашения", 237 + "invalidTitle": "Недействительное приглашение", 238 + "invitationExpired": "Приглашение истекло", 239 + "errorLoadDescription": "Не удалось загрузить данные приглашения. Приглашение может быть недействительным или просроченным.", 240 + "goToSignIn": "Перейти к входу", 241 + "workspaceLabel": "Рабочая область: {{workspaceName}}", 242 + "joinWorkspace": "Присоединиться к {{workspaceName}}", 243 + "inviteBodySignedIn": "<inviter>{{inviterName}}</inviter> приглашает вас присоединиться к рабочей области.", 244 + "inviteBodySignedOut": "<inviter>{{inviterName}}</inviter> приглашает вас присоединиться к рабочей области в Kaneo.", 245 + "signInToAccept": "Войдите, чтобы принять приглашение.", 246 + "accepting": "Принятие...", 247 + "acceptInvitation": "Принять приглашение", 248 + "goToDashboard": "Перейти к панели управления", 249 + "signedInAs": "Вы вошли как <email>{{email}}</email>", 250 + "youveBeenInvited": "Вас пригласили!", 251 + "invitationFor": "Приглашение для: <email>{{email}}</email>", 252 + "signIn": "Войти", 253 + "toast": { 254 + "acceptFailed": "Не удалось принять приглашение", 255 + "acceptSuccess": "Приглашение принято! Добро пожаловать в команду." 256 + } 257 + }, 258 + "onboarding": { 259 + "pageTitle": "Добро пожаловать в Kaneo", 260 + "workspacePageTitle": "Создание рабочей области", 261 + "createWorkspaceTitle": "Создать рабочую область", 262 + "createWorkspaceSubtitle": "Настройте рабочую область, чтобы начать управление проектами", 263 + "workspaceName": "Название рабочей области", 264 + "workspaceNamePlaceholder": "например, Acme Inc, Моя команда", 265 + "descriptionOptional": "Описание (необязательно)", 266 + "descriptionPlaceholder": "Чем занимается ваша команда?", 267 + "creating": "Создание...", 268 + "createWorkspace": "Создать рабочую область", 269 + "workspaceCreatedTitle": "Рабочая область создана", 270 + "redirectingToWorkspace": "Переход к <name>{{name}}</name>...", 271 + "toast": { 272 + "workspaceCreated": "Рабочая область успешно создана", 273 + "createFailed": "Не удалось создать рабочую область" 274 + }, 275 + "validation": { 276 + "workspaceNameRequired": "Название рабочей области обязательно" 277 + } 278 + }, 279 + "profileSetup": { 280 + "pageTitle": "Заполнение профиля", 281 + "completeTitle": "Заполните свой профиль", 282 + "subtitle": "Пожалуйста, введите своё имя, чтобы начать", 283 + "yourName": "Ваше имя", 284 + "namePlaceholder": "например, Иван Иванов", 285 + "saving": "Сохранение...", 286 + "continue": "Продолжить", 287 + "welcome": "Добро пожаловать, {{name}}!", 288 + "redirecting": "Переход к панели управления...", 289 + "toast": { 290 + "updateSuccess": "Профиль успешно обновлён", 291 + "updateFailed": "Не удалось обновить профиль" 292 + }, 293 + "validation": { 294 + "nameRequired": "Имя обязательно", 295 + "nameShort": "Имя должно содержать не менее 2 символов" 296 + } 297 + } 298 + }, 299 + "settings": { 300 + "account": "Аккаунт", 301 + "developer": "Разработчик", 302 + "information": "Информация", 303 + "notifications": "Уведомления", 304 + "preferences": "Настройки", 305 + "apiKeys": "API-ключи", 306 + "informationPage": { 307 + "pageTitle": "Личная информация", 308 + "title": "Личная информация", 309 + "subtitle": "Управляйте вашими персональными данными и информацией об аккаунте.", 310 + "sectionTitle": "Информация об аккаунте", 311 + "sectionSubtitle": "Управляйте вашим профилем и данными аккаунта.", 312 + "profilePicture": "Фото профиля", 313 + "fullName": "Полное имя", 314 + "fullNamePlaceholder": "Введите ваше имя", 315 + "email": "Электронная почта", 316 + "emailPlaceholder": "Введите вашу почту", 317 + "updateSuccess": "Профиль успешно обновлён", 318 + "updateError": "Не удалось обновить профиль", 319 + "validation": { 320 + "nameRequired": "Имя обязательно", 321 + "nameShort": "Имя должно содержать не менее 2 символов", 322 + "invalidEmail": "Некорректный адрес электронной почты" 323 + } 324 + }, 325 + "notificationsPage": { 326 + "pageTitle": "Уведомления", 327 + "title": "Уведомления", 328 + "subtitle": "Выберите, как Kaneo доставляет уведомления и какие каналы использовать.", 329 + "statusConnected": "Подключено", 330 + "statusPaused": "Приостановлено", 331 + "emailTitle": "Электронная почта", 332 + "emailDescription": "Используйте адрес вашего аккаунта для получения уведомлений.", 333 + "accountEmailLabel": "Почта аккаунта", 334 + "accountEmailNoAddress": "Адрес электронной почты недоступен", 335 + "accountEmailHint": "Уведомления всегда отправляются на адрес электронной почты текущего аккаунта.", 336 + "saveChanges": "Сохранить изменения", 337 + "disconnect": "Отключить", 338 + "ntfyTitle": "ntfy", 339 + "ntfyDescription": "Отправляйте уведомления аккаунта в тему ntfy.", 340 + "serverUrl": "URL сервера", 341 + "topic": "Тема", 342 + "token": "Токен", 343 + "ntfyServerPlaceholder": "https://ntfy.example.com", 344 + "ntfyTopicPlaceholder": "team-alerts", 345 + "ntfyTokenPlaceholder": "Необязательный bearer-токен", 346 + "ntfyTokenHintConfigured": "Токен уже настроен ({{masked}}). Введите новый токен для замены.", 347 + "ntfyTokenHintOptional": "Необязательно. Укажите токен, если ваш сервер ntfy требует аутентификации.", 348 + "connectNtfy": "Подключить ntfy", 349 + "gotifyTitle": "Gotify", 350 + "gotifyDescription": "Отправляйте уведомления аккаунта на ваш сервер Gotify.", 351 + "gotifyTokenLabel": "Токен приложения", 352 + "gotifyServerPlaceholder": "https://gotify.example.com", 353 + "gotifyTokenPlaceholder": "Токен приложения Gotify", 354 + "gotifyTokenHintConfigured": "Токен приложения уже настроен ({{masked}}). Введите новый токен для замены.", 355 + "gotifyTokenHintRequired": "Обязательно. Используйте токен приложения с вашего сервера Gotify.", 356 + "connectGotify": "Подключить Gotify", 357 + "webhookTitle": "Пользовательский вебхук", 358 + "webhookDescription": "Отправляйте уведомления аккаунта на ваш эндпоинт в формате JSON.", 359 + "endpointUrl": "URL эндпоинта", 360 + "signingSecret": "Секрет подписи", 361 + "webhookUrlPlaceholder": "https://example.com/webhooks/kaneo", 362 + "webhookSecretPlaceholder": "Необязательный общий секрет", 363 + "webhookSecretHintConfigured": "Секрет подписи уже настроен ({{masked}}). Введите новый для замены.", 364 + "webhookSecretHintOptional": "Необязательно. Kaneo подписывает тело запроса, если секрет задан.", 365 + "connectWebhook": "Подключить вебхук", 366 + "workspaceRulesTitle": "Правила доставки для рабочих областей", 367 + "workspaceRulesDescription": "Используйте глобальные каналы и определяйте, какие рабочие области и проекты могут отправлять уведомления.", 368 + "workspaceCardHint": "Выберите, какие каналы может использовать эта рабочая область для уведомлений.", 369 + "workspaceCardLabelEmail": "Почта", 370 + "workspaceCardLabelNtfy": "ntfy", 371 + "workspaceCardLabelGotify": "Gotify", 372 + "workspaceCardLabelWebhook": "Пользовательский вебхук", 373 + "emailChannelHintEnabled": "Отправлять подходящие уведомления рабочей области по электронной почте.", 374 + "emailChannelHintDisabled": "Сначала настройте и включите электронную почту глобально.", 375 + "ntfyChannelHintEnabled": "Отправлять подходящие уведомления рабочей области в ntfy.", 376 + "ntfyChannelHintDisabled": "Сначала настройте и включите ntfy глобально.", 377 + "gotifyChannelHintEnabled": "Отправлять подходящие уведомления рабочей области в Gotify.", 378 + "gotifyChannelHintDisabled": "Сначала настройте и включите Gotify глобально.", 379 + "webhookChannelHintEnabled": "Отправлять подходящие уведомления рабочей области на вебхук.", 380 + "webhookChannelHintDisabled": "Сначала настройте и включите вебхук глобально.", 381 + "projectScope": "Область проектов", 382 + "projectScopeDescription": "По умолчанию включены все проекты. Сузьте выборку, если нужны уведомления только от определённых проектов.", 383 + "allProjects": "Все проекты", 384 + "allProjectsDescription": "Доставлять уведомления от всех проектов в этой рабочей области.", 385 + "selectedProjects": "Выбранные проекты", 386 + "selectedProjectsDescription": "Доставлять уведомления только от выбранных проектов.", 387 + "noProjectsInWorkspace": "В этой рабочей области пока нет проектов.", 388 + "createRule": "Создать правило", 389 + "removeRule": "Удалить правило", 390 + "toastEmailSaved": "Настройки email-уведомлений сохранены", 391 + "toastEmailSaveFailed": "Не удалось сохранить настройки email", 392 + "toastNtfySaved": "Настройки ntfy сохранены", 393 + "toastNtfySaveFailed": "Не удалось сохранить настройки ntfy", 394 + "toastNtfyDisconnected": "ntfy отключён", 395 + "toastNtfyDisconnectFailed": "Не удалось отключить ntfy", 396 + "toastGotifySaved": "Настройки Gotify сохранены", 397 + "toastGotifySaveFailed": "Не удалось сохранить настройки Gotify", 398 + "toastGotifyDisconnected": "Gotify отключён", 399 + "toastGotifyDisconnectFailed": "Не удалось отключить Gotify", 400 + "toastWebhookSaved": "Настройки вебхука сохранены", 401 + "toastWebhookSaveFailed": "Не удалось сохранить настройки вебхука", 402 + "toastWebhookDisconnected": "Вебхук отключён", 403 + "toastWebhookDisconnectFailed": "Не удалось отключить вебхук", 404 + "toastRuleSaved": "Правило уведомлений для {{workspaceName}} сохранено", 405 + "toastRuleSaveFailed": "Не удалось сохранить правило уведомлений для рабочей области", 406 + "toastRuleRemoved": "Правило уведомлений для {{workspaceName}} удалено", 407 + "toastRuleRemoveFailed": "Не удалось удалить правило уведомлений для рабочей области", 408 + "toastPreferencesSaved": "Настройки уведомлений сохранены", 409 + "toastPreferencesSaveFailed": "Не удалось сохранить настройки уведомлений", 410 + "toastRuleSavedGeneric": "Правило уведомлений для рабочей области сохранено", 411 + "toastRuleRemovedGeneric": "Правило уведомлений для рабочей области удалено" 412 + }, 413 + "preferencesPage": { 414 + "title": "Настройки", 415 + "subtitle": "Настройте Kaneo под себя.", 416 + "appearanceTitle": "Внешний вид", 417 + "appearanceSubtitle": "Визуальные настройки и параметры интерфейса.", 418 + "theme": "Тема", 419 + "themeDescription": "Выберите предпочтительную цветовую схему", 420 + "selectTheme": "Выберите тему", 421 + "themeLight": "Светлая", 422 + "themeDark": "Тёмная", 423 + "themeSystem": "Системная", 424 + "language": "Язык", 425 + "languageDescription": "Выберите предпочтительный язык интерфейса", 426 + "selectLanguage": "Выберите язык", 427 + "defaultView": "Вид по умолчанию", 428 + "defaultViewDescription": "Выберите предпочтительный режим отображения задач", 429 + "selectViewMode": "Выберите режим просмотра", 430 + "board": "Доска", 431 + "list": "Список", 432 + "gantt": "Гант", 433 + "sidebarDefault": "Боковая панель по умолчанию", 434 + "sidebarDefaultDescription": "Оставлять боковую панель раскрытой при запуске", 435 + "displayOptions": "Параметры отображения", 436 + "displayOptionsDescription": "Выберите, какую информацию показывать в представлениях задач", 437 + "taskNumbers": "Номера задач", 438 + "taskNumbersDescription": "Показывать идентификаторы и номера задач", 439 + "assignees": "Исполнители", 440 + "assigneesDescription": "Показывать, кому назначены задачи", 441 + "dueDates": "Сроки выполнения", 442 + "dueDatesDescription": "Показывать сроки выполнения задач", 443 + "labels": "Метки", 444 + "labelsDescription": "Показывать метки и теги задач", 445 + "priority": "Приоритет", 446 + "priorityDescription": "Показывать индикаторы приоритета" 447 + }, 448 + "developerPage": { 449 + "pageTitle": "Настройки разработчика", 450 + "title": "Настройки разработчика", 451 + "subtitle": "Управляйте вашими API-ключами и ресурсами для разработчиков.", 452 + "apiKeysCardTitle": "API-ключи", 453 + "apiKeysCardDescription": "Создавайте и управляйте API-ключами для программного доступа к Kaneo.", 454 + "createApiKey": "Создать API-ключ", 455 + "unnamedKey": "Безымянный ключ" 456 + }, 457 + "apiKey": { 458 + "createdModal": { 459 + "title": "API-ключ создан", 460 + "description": "Ваш API-ключ «{{keyName}}» успешно создан.", 461 + "yourApiKey": "Ваш API-ключ", 462 + "copy": "Копировать", 463 + "copied": "Скопировано", 464 + "toastCopied": "API-ключ скопирован в буфер обмена", 465 + "alertTitle": "Готово! Ваш API-ключ создан", 466 + "alertDescription": "Скопируйте этот ключ сейчас. Вы больше не сможете его увидеть.", 467 + "done": "Готово", 468 + "copyToContinue": "Скопируйте ключ, чтобы продолжить" 469 + }, 470 + "table": { 471 + "loading": "Загрузка API-ключей...", 472 + "empty": "API-ключей пока нет. Создайте первый, чтобы начать.", 473 + "columnName": "Название", 474 + "columnKey": "Ключ", 475 + "columnCreated": "Создан", 476 + "columnExpires": "Истекает", 477 + "columnActions": "Действия", 478 + "unnamedKey": "Безымянный ключ", 479 + "expiredBadge": "Истёк {{label}}", 480 + "deleteConfirmTitle": "Удалить API-ключ?", 481 + "deleteConfirmDescription": "Это действие нельзя отменить. API-ключ {{name}} будет удалён навсегда.", 482 + "deleteFallbackName": "этот API-ключ", 483 + "delete": "Удалить", 484 + "deleting": "Удаление...", 485 + "deleteAria": "Удалить {{name}}", 486 + "deleteAriaFallback": "API-ключ", 487 + "toastDeleted": "API-ключ успешно удалён", 488 + "toastDeleteError": "Не удалось удалить API-ключ" 489 + }, 490 + "createDialog": { 491 + "title": "Создать API-ключ", 492 + "description": "Создайте новый API-ключ для программного доступа к Kaneo API.", 493 + "nameLabel": "Название", 494 + "namePlaceholder": "Мой API-ключ", 495 + "nameDescription": "Описательное название для этого API-ключа", 496 + "expirationLabel": "Срок действия", 497 + "expirationPlaceholder": "Выберите срок действия", 498 + "expirationDescription": "Выберите, как долго этот API-ключ будет действителен. «Бессрочно» создаст ключ без автоматического истечения.", 499 + "expiration1d": "1 день", 500 + "expiration7d": "7 дней", 501 + "expiration30d": "30 дней", 502 + "expiration90d": "90 дней", 503 + "expirationNever": "Бессрочно", 504 + "create": "Создать", 505 + "creating": "Создание...", 506 + "failedCreate": "Не удалось создать API-ключ", 507 + "validation": { 508 + "nameRequired": "Название обязательно", 509 + "nameShort": "Название должно содержать не менее 3 символов", 510 + "expirationRequired": "Срок действия обязателен" 511 + } 512 + } 513 + }, 514 + "workspaceGeneral": { 515 + "pageTitle": "Основные настройки", 516 + "title": "Основные настройки", 517 + "subtitle": "Управляйте названием и описанием рабочей области.", 518 + "workspaceInfoTitle": "Информация о рабочей области", 519 + "workspaceInfoSubtitle": "Настройте параметры и предпочтения рабочей области.", 520 + "nameLabel": "Название рабочей области", 521 + "nameHint": "Название вашей рабочей области", 522 + "namePlaceholder": "Введите название рабочей области", 523 + "descriptionLabel": "Описание", 524 + "descriptionHint": "Краткое описание вашей рабочей области", 525 + "descriptionPlaceholder": "Введите описание рабочей области", 526 + "dangerZone": "Опасная зона", 527 + "dangerZoneSubtitle": "Необратимые и деструктивные действия.", 528 + "deleteWorkspace": "Удалить рабочую область", 529 + "deleteWorkspaceDescription": "Запланировать безвозвратное удаление рабочей области", 530 + "deleteModalTitle": "Удалить рабочую область?", 531 + "deleteModalDescription": "Рабочая область «{{name}}» и все её данные будут безвозвратно удалены. Это действие нельзя отменить.", 532 + "deleteModalConfirm": "Удалить рабочую область", 533 + "toastUpdated": "Рабочая область успешно обновлена", 534 + "toastUpdateError": "Не удалось обновить рабочую область", 535 + "toastDeleted": "Рабочая область успешно удалена", 536 + "toastDeleteError": "Не удалось удалить рабочую область", 537 + "validation": { 538 + "nameRequired": "Название рабочей области обязательно", 539 + "nameShort": "Название рабочей области должно содержать не менее 2 символов" 540 + } 541 + }, 542 + "projectGeneral": { 543 + "pageTitle": "Настройки проекта", 544 + "title": "Основные настройки", 545 + "subtitle": "Управляйте названием, ключом, иконкой и описанием проекта.", 546 + "projectInfoTitle": "Информация о проекте", 547 + "projectInfoSubtitle": "Настройте параметры и предпочтения проекта.", 548 + "iconLabel": "Иконка", 549 + "iconHint": "Отображается в боковой панели и на страницах проекта.", 550 + "pickIconTitle": "Выбрать иконку", 551 + "searchIconsPlaceholder": "Поиск иконок...", 552 + "projectNameLabel": "Название проекта", 553 + "projectNameHint": "Название вашего проекта", 554 + "projectNamePlaceholder": "Введите название проекта", 555 + "keyLabel": "Ключ", 556 + "keyHint": "Используется для идентификаторов задач (например, {{slug}}-123)", 557 + "keyPlaceholder": "PRO", 558 + "descriptionLabel": "Описание", 559 + "descriptionHint": "Краткое описание вашего проекта", 560 + "descriptionPlaceholder": "Введите описание проекта", 561 + "dangerZone": "Опасная зона", 562 + "dangerZoneSubtitle": "Необратимые и деструктивные действия.", 563 + "deleteProject": "Удалить проект", 564 + "deleteProjectDescription": "Запланировать безвозвратное удаление проекта", 565 + "deleteModalTitle": "Удалить проект?", 566 + "deleteModalDescription": "Проект «{{name}}» и все его данные будут безвозвратно удалены. Это действие нельзя отменить.", 567 + "deleteModalConfirm": "Удалить проект", 568 + "toastUpdated": "Проект успешно обновлён", 569 + "toastUpdateError": "Не удалось обновить проект", 570 + "toastDeleted": "Проект успешно удалён", 571 + "toastDeleteError": "Не удалось удалить проект", 572 + "validation": { 573 + "nameRequired": "Название проекта обязательно", 574 + "nameShort": "Название проекта должно содержать не менее 2 символов", 575 + "keyRequired": "Ключ обязателен", 576 + "keyShort": "Ключ должен содержать не менее 2 символов", 577 + "keyMax": "Ключ должен содержать не более 8 символов", 578 + "iconRequired": "Иконка обязательна" 579 + } 580 + }, 581 + "projectIntegrations": { 582 + "pageTitle": "Интеграции проекта", 583 + "title": "Интеграции проекта", 584 + "subtitle": "Подключите ваш проект к внешним инструментам и сервисам для оптимизации рабочего процесса.", 585 + "githubSectionTitle": "Интеграция с GitHub", 586 + "githubSectionSubtitle": "Синхронизация задач с GitHub Issues и двусторонняя синхронизация.", 587 + "giteaSectionTitle": "Интеграция с Gitea", 588 + "giteaSectionSubtitle": "Синхронизация задач с вашим экземпляром Gitea (задачи, PR, вебхуки).", 589 + "discordSectionTitle": "Интеграция с Discord", 590 + "discordSectionSubtitle": "Отправка обновлений по задачам проекта в канал Discord через вебхук.", 591 + "genericWebhookSectionTitle": "Пользовательские вебхуки", 592 + "genericWebhookSectionSubtitle": "Отправка событий по задачам проекта на любой HTTP-эндпоинт в формате JSON.", 593 + "slackSectionTitle": "Интеграция со Slack", 594 + "slackSectionSubtitle": "Отправка обновлений по задачам проекта в канал Slack через входящий вебхук.", 595 + "telegramSectionTitle": "Интеграция с Telegram", 596 + "telegramSectionSubtitle": "Отправка обновлений по задачам проекта в чат или тему Telegram через бота." 597 + }, 598 + "projectVisibility": { 599 + "pageTitle": "Видимость проекта", 600 + "title": "Видимость", 601 + "subtitle": "Управляйте доступом к вашему проекту.", 602 + "sectionTitle": "Видимость", 603 + "sectionSubtitle": "Переключайте публичный доступ и делитесь публичной ссылкой.", 604 + "publicAccess": "Публичный доступ", 605 + "publicAccessHint": "Разрешить доступ к проекту всем, у кого есть ссылка", 606 + "publicUrl": "Публичная ссылка", 607 + "publicUrlHint": "Поделитесь этой ссылкой, если проект публичный", 608 + "copy": "Копировать", 609 + "copiedToast": "Скопировано", 610 + "toastUpdated": "Видимость обновлена", 611 + "toastUpdateError": "Не удалось обновить видимость" 612 + }, 613 + "projectWorkflow": { 614 + "pageTitle": "Настройки рабочего процесса", 615 + "title": "Рабочий процесс", 616 + "subtitle": "Настройте колонки доски и правила автоматизации для этого проекта.", 617 + "columnsTitle": "Колонки", 618 + "columnsDescription": "Управляйте колонками на доске. Перетаскивайте для изменения порядка. Включите «Колонка завершения» для этапов, обозначающих выполненную работу.", 619 + "automationTitle": "Правила автоматизации", 620 + "automationDescription": "Привяжите события интеграций к колонкам. При наступлении события связанная задача перемещается в указанную колонку." 621 + }, 622 + "projectSwitcher": { 623 + "selectProject": "Выберите проект", 624 + "noProjects": "Нет проектов" 625 + }, 626 + "columnEditor": { 627 + "toastCreated": "Колонка создана", 628 + "toastCreateError": "Не удалось создать колонку", 629 + "toastRenamed": "Колонка переименована", 630 + "toastRenameError": "Не удалось обновить колонку", 631 + "toastFinalOn": "Колонка отмечена как завершающая", 632 + "toastFinalOff": "Колонка больше не завершающая", 633 + "toastUpdateError": "Не удалось обновить колонку", 634 + "toastDeleted": "Колонка удалена", 635 + "toastDeleteError": "Не удалось удалить колонку", 636 + "loading": "Загрузка колонок...", 637 + "doneColumnTooltip": "Считать эту колонку завершающей", 638 + "doneColumn": "Колонка завершения", 639 + "markDoneAria": "Отметить {{name}} как колонку завершения", 640 + "on": "Вкл", 641 + "off": "Выкл", 642 + "newColumnPlaceholder": "Название новой колонки...", 643 + "add": "Добавить" 644 + }, 645 + "githubIntegration": { 646 + "validation": { 647 + "ownerRequired": "Владелец репозитория обязателен", 648 + "ownerInvalid": "Некорректный формат владельца репозитория", 649 + "nameRequired": "Название репозитория обязательно", 650 + "nameInvalid": "Некорректный формат названия репозитория" 651 + }, 652 + "toast": { 653 + "installedOk": "GitHub App установлено корректно!", 654 + "installedMissingPerms": "GitHub App установлено, но не хватает прав", 655 + "needsInstallOnRepo": "GitHub App необходимо установить на этот репозиторий", 656 + "repoNotFound": "Репозиторий не найден или недоступен", 657 + "verifyError": "Не удалось проверить установку GitHub", 658 + "installAppFirst": "Сначала установите GitHub App на этот репозиторий", 659 + "missingPermsDetail": "GitHub App не хватает прав: {{list}}. Пожалуйста, обновите права приложения.", 660 + "updated": "Интеграция с GitHub успешно обновлена", 661 + "updateError": "Не удалось обновить интеграцию с GitHub", 662 + "removed": "Интеграция с GitHub успешно удалена", 663 + "removeError": "Не удалось удалить интеграцию с GitHub", 664 + "issuesImported": "Issues успешно импортированы", 665 + "importError": "Не удалось импортировать issues", 666 + "commentOnEnabled": "Kaneo будет комментировать новые issues ссылкой на задачу", 667 + "commentOnDisabled": "Комментарии со ссылками на задачи в новых issues отключены", 668 + "settingsUpdateError": "Не удалось обновить интеграцию с GitHub" 669 + }, 670 + "connectionStatus": "Статус подключения", 671 + "connectedActive": "Репозиторий подключён и активен", 672 + "notConnectedHint": "Репозиторий не подключён", 673 + "badgeConnected": "Подключён", 674 + "badgeNotConnected": "Не подключён", 675 + "repository": "Репозиторий", 676 + "repositoryHint": "Подключённый репозиторий GitHub", 677 + "commentTaskLinkTitle": "Комментировать ссылкой Kaneo в новых issues", 678 + "commentTaskLinkHint": "Когда включено, Kaneo оставляет комментарий в каждом новом GitHub issue со ссылкой на задачу.", 679 + "appStatusTitle": "Статус GitHub App", 680 + "appStatusHint": "Статус установки и прав", 681 + "statusProperlyConfigured": "Настроено корректно", 682 + "statusMissingPermissions": "Не хватает прав", 683 + "statusNotInstalled": "Не установлено", 684 + "ownerLabel": "Владелец репозитория", 685 + "ownerHint": "Имя пользователя или организации GitHub", 686 + "ownerPlaceholder": "например, octocat", 687 + "repoNameLabel": "Название репозитория", 688 + "repoNameHint": "Название репозитория", 689 + "repoNamePlaceholder": "например, my-project", 690 + "actionsTitle": "Действия", 691 + "actionsHint": "Управление подключением к репозиторию", 692 + "browse": "Обзор", 693 + "verify": "Проверить", 694 + "update": "Обновить", 695 + "connect": "Подключить", 696 + "disconnect": "Отключить", 697 + "missingPermissionsLabel": "Недостающие права:", 698 + "updatePermissions": "Обновить права", 699 + "installGithubApp": "Установить GitHub App", 700 + "importSectionTitle": "Импорт GitHub Issues", 701 + "importSectionHint": "Импортируйте существующие issues из вашего GitHub-репозитория как задачи", 702 + "importing": "Импорт...", 703 + "importIssues": "Импортировать Issues", 704 + "importDisabledHint": "Завершите настройку репозитория выше, чтобы включить импорт" 705 + }, 706 + "giteaIntegration": { 707 + "validation": { 708 + "baseUrlRequired": "URL Gitea обязателен", 709 + "baseUrlInvalid": "Введите корректный URL (например, https://gitea.example.com)", 710 + "ownerRequired": "Владелец репозитория обязателен", 711 + "ownerInvalid": "Некорректный формат владельца репозитория", 712 + "nameRequired": "Название репозитория обязательно", 713 + "nameInvalid": "Некорректный формат названия репозитория" 714 + }, 715 + "toast": { 716 + "verifyOk": "Токен Gitea может получить доступ к этому репозиторию", 717 + "verifyWarning": "Проверьте права токена или доступ к репозиторию", 718 + "repoNotFound": "Репозиторий не найден или недоступен", 719 + "verifyError": "Не удалось проверить доступ к Gitea", 720 + "tokenRequired": "Требуется персональный токен доступа", 721 + "tokenRequiredVerify": "Введите токен для проверки", 722 + "verifyFirst": "Проверка доступа не удалась — проверьте URL, токен и репозиторий", 723 + "updated": "Интеграция с Gitea сохранена", 724 + "updateError": "Не удалось сохранить интеграцию с Gitea", 725 + "removed": "Интеграция с Gitea удалена", 726 + "removeError": "Не удалось удалить интеграцию с Gitea", 727 + "issuesImported": "Issues успешно импортированы", 728 + "importError": "Не удалось импортировать issues", 729 + "commentOnEnabled": "Kaneo будет комментировать новые issues ссылкой на задачу", 730 + "commentOnDisabled": "Комментарии со ссылками на задачи в новых issues отключены", 731 + "settingsUpdateError": "Не удалось обновить интеграцию с Gitea", 732 + "secretCopied": "Скопировано", 733 + "unableToCopySecret": "Не удалось скопировать секрет" 734 + }, 735 + "webhookShow": "Показать", 736 + "webhookHide": "Скрыть", 737 + "webhookCopy": "Копировать", 738 + "connectionStatus": "Статус подключения", 739 + "connectedActive": "Репозиторий подключён и активен", 740 + "notConnectedHint": "Репозиторий Gitea не подключён", 741 + "badgeConnected": "Подключён", 742 + "badgeNotConnected": "Не подключён", 743 + "repository": "Репозиторий", 744 + "repositoryHint": "Привязанный репозиторий Gitea", 745 + "commentTaskLinkTitle": "Комментировать ссылкой Kaneo в новых issues", 746 + "commentTaskLinkHint": "Когда включено, Kaneo оставляет комментарий в каждом новом issue со ссылкой на задачу.", 747 + "webhookTitle": "Вебхук", 748 + "webhookHint": "В вашем репозитории Gitea добавьте вебхук с этим URL и секретом. Включите события: push, pull request, issues, комментарии к issues и создание (для меток).", 749 + "webhookSecretLabel": "Секрет (должен совпадать с секретом вебхука в Gitea)", 750 + "baseUrlLabel": "URL Gitea", 751 + "baseUrlHint": "Корневой URL вашего экземпляра Gitea", 752 + "tokenLabel": "Персональный токен доступа", 753 + "tokenHint": "Токен с доступом к репозиторию и issues", 754 + "tokenPlaceholder": "Вставьте токен", 755 + "tokenPlaceholderUpdate": "Вставьте новый токен для замены", 756 + "currentToken": "сохранён", 757 + "ownerLabel": "Владелец", 758 + "ownerHint": "Имя пользователя или организации", 759 + "repoNameLabel": "Название репозитория", 760 + "repoNameHint": "Только название репозитория (без владельца)", 761 + "actionsTitle": "Действия", 762 + "actionsHint": "Проверить доступ и подключить", 763 + "browse": "Обзор", 764 + "verify": "Проверить", 765 + "update": "Обновить", 766 + "connect": "Подключить", 767 + "disconnect": "Отключить", 768 + "importSectionTitle": "Импорт issues из Gitea", 769 + "importSectionHint": "Импортируйте открытые issues и pull requests из репозитория", 770 + "importing": "Импорт…", 771 + "importIssues": "Импортировать issues", 772 + "importDisabledHint": "Проверьте репозиторий выше, чтобы включить импорт", 773 + "browseModalTitle": "Ваши репозитории", 774 + "browseModalHint": "Репозитории, к которым у вашего токена есть доступ", 775 + "searchRepos": "Поиск…", 776 + "browseNeedsCredentials": "Введите URL Gitea и токен для просмотра", 777 + "loadingRepos": "Загрузка репозиториев…", 778 + "retry": "Повторить" 779 + }, 780 + "slackIntegration": { 781 + "validation": { 782 + "webhookInvalid": "Введите корректный URL вебхука Slack" 783 + }, 784 + "toast": { 785 + "saved": "Интеграция со Slack успешно сохранена", 786 + "saveError": "Не удалось сохранить интеграцию со Slack", 787 + "enabled": "Уведомления Slack включены", 788 + "disabled": "Уведомления Slack приостановлены", 789 + "updateError": "Не удалось обновить интеграцию со Slack", 790 + "removed": "Интеграция со Slack успешно удалена", 791 + "removeError": "Не удалось удалить интеграцию со Slack" 792 + }, 793 + "connectionTitle": "Подключение вебхука Slack", 794 + "connectionHint": "Вставьте URL входящего вебхука Slack и выберите, какие события задач отправлять.", 795 + "connected": "Подключён", 796 + "paused": "Приостановлен", 797 + "webhookLabel": "URL входящего вебхука", 798 + "webhookPlaceholder": "https://hooks.slack.com/services/...", 799 + "webhookHint": "Создайте входящий вебхук в Slack и вставьте сгенерированный URL сюда.", 800 + "channelLabel": "Название канала", 801 + "channelPlaceholder": "#team-updates", 802 + "channelHint": "Необязательная метка для справки. Slack определяет канал назначения из настроек вебхука.", 803 + "eventsTitle": "Уведомления о событиях", 804 + "eventsHint": "Выберите, какие изменения проекта отправлять в Slack.", 805 + "events": { 806 + "taskCreated": "Новые задачи", 807 + "taskStatusChanged": "Изменения статуса", 808 + "taskPriorityChanged": "Изменения приоритета", 809 + "taskTitleChanged": "Изменения названия", 810 + "taskDescriptionChanged": "Изменения описания", 811 + "taskCommentCreated": "Новые комментарии" 812 + }, 813 + "connect": "Подключить Slack", 814 + "saveChanges": "Сохранить изменения", 815 + "update": "Обновить Slack", 816 + "disconnect": "Отключить" 817 + }, 818 + "discordIntegration": { 819 + "validation": { 820 + "webhookInvalid": "Введите корректный URL вебхука Discord" 821 + }, 822 + "toast": { 823 + "saved": "Интеграция с Discord успешно сохранена", 824 + "saveError": "Не удалось сохранить интеграцию с Discord", 825 + "enabled": "Уведомления Discord включены", 826 + "disabled": "Уведомления Discord приостановлены", 827 + "updateError": "Не удалось обновить интеграцию с Discord", 828 + "removed": "Интеграция с Discord успешно удалена", 829 + "removeError": "Не удалось удалить интеграцию с Discord" 830 + }, 831 + "connectionTitle": "Подключение вебхука Discord", 832 + "connectionHint": "Вставьте URL вебхука Discord и выберите, какие события задач отправлять.", 833 + "connected": "Подключён", 834 + "paused": "Приостановлен", 835 + "webhookLabel": "URL вебхука", 836 + "webhookPlaceholder": "https://discord.com/api/webhooks/...", 837 + "webhookHint": "Создайте вебхук канала Discord и вставьте сгенерированный URL сюда.", 838 + "channelLabel": "Название канала", 839 + "channelPlaceholder": "#team-updates", 840 + "channelHint": "Необязательная метка для справки. Discord определяет канал назначения из настроек вебхука.", 841 + "eventsTitle": "Уведомления о событиях", 842 + "eventsHint": "Выберите, какие изменения проекта отправлять в Discord.", 843 + "events": { 844 + "taskCreated": "Новые задачи", 845 + "taskStatusChanged": "Изменения статуса", 846 + "taskPriorityChanged": "Изменения приоритета", 847 + "taskTitleChanged": "Изменения названия", 848 + "taskDescriptionChanged": "Изменения описания", 849 + "taskCommentCreated": "Новые комментарии" 850 + }, 851 + "connect": "Подключить Discord", 852 + "saveChanges": "Сохранить изменения", 853 + "update": "Обновить Discord", 854 + "disconnect": "Отключить" 855 + }, 856 + "genericWebhookIntegration": { 857 + "validation": { 858 + "webhookInvalid": "Введите корректный URL вебхука" 859 + }, 860 + "toast": { 861 + "saved": "Пользовательский вебхук успешно сохранён", 862 + "saveError": "Не удалось сохранить пользовательский вебхук", 863 + "enabled": "Уведомления пользовательского вебхука включены", 864 + "disabled": "Уведомления пользовательского вебхука приостановлены", 865 + "updateError": "Не удалось обновить пользовательский вебхук", 866 + "removed": "Пользовательский вебхук успешно удалён", 867 + "removeError": "Не удалось удалить пользовательский вебхук" 868 + }, 869 + "connectionTitle": "Исходящий вебхук", 870 + "connectionHint": "Отправляйте события задач на ваш эндпоинт в формате JSON. Подписанный заголовок X-Kaneo-Signature включается, если настроен секрет.", 871 + "connected": "Подключён", 872 + "paused": "Приостановлен", 873 + "webhookLabel": "URL эндпоинта", 874 + "webhookPlaceholder": "https://example.com/webhooks/kaneo", 875 + "webhookHint": "Kaneo отправляет POST-запросы с JSON-данными для каждого включённого события.", 876 + "secretLabel": "Секрет подписи", 877 + "secretPlaceholder": "Необязательный общий секрет", 878 + "secretHint": "Необязательно. Если задан, Kaneo подписывает тело запроса и отправляет hex-дайджест в заголовке X-Kaneo-Signature.", 879 + "secretHintConfigured": "Секрет подписи уже настроен ({{secret}}). Введите новый для замены.", 880 + "eventsTitle": "Уведомления о событиях", 881 + "eventsHint": "Выберите, какие изменения проекта должны вызывать исходящие вебхуки.", 882 + "events": { 883 + "taskCreated": "Новые задачи", 884 + "taskStatusChanged": "Изменения статуса", 885 + "taskPriorityChanged": "Изменения приоритета", 886 + "taskTitleChanged": "Изменения названия", 887 + "taskDescriptionChanged": "Изменения описания", 888 + "taskCommentCreated": "Новые комментарии" 889 + }, 890 + "connect": "Подключить вебхук", 891 + "saveChanges": "Сохранить изменения", 892 + "disconnect": "Отключить" 893 + }, 894 + "telegramIntegration": { 895 + "validation": { 896 + "botTokenInvalid": "Введите корректный токен Telegram-бота", 897 + "chatIdRequired": "ID чата обязателен", 898 + "threadIdInvalid": "Введите корректный ID темы Telegram" 899 + }, 900 + "toast": { 901 + "saved": "Интеграция с Telegram успешно сохранена", 902 + "saveError": "Не удалось сохранить интеграцию с Telegram", 903 + "enabled": "Уведомления Telegram включены", 904 + "disabled": "Уведомления Telegram приостановлены", 905 + "updateError": "Не удалось обновить интеграцию с Telegram", 906 + "removed": "Интеграция с Telegram успешно удалена", 907 + "removeError": "Не удалось удалить интеграцию с Telegram" 908 + }, 909 + "connectionTitle": "Подключение Telegram-бота", 910 + "connectionHint": "Используйте токен Telegram-бота и ID чата для отправки обновлений по задачам в чат или тему.", 911 + "connected": "Подключён", 912 + "paused": "Приостановлен", 913 + "botTokenLabel": "Токен бота", 914 + "botTokenPlaceholder": "123456789:AAExampleBotToken", 915 + "botTokenHint": "Создайте бота через BotFather и вставьте его токен сюда.", 916 + "botTokenHintConfigured": "Токен бота уже настроен ({{token}}). Введите новый для замены.", 917 + "chatIdLabel": "ID чата", 918 + "chatIdPlaceholder": "-1001234567890 или @team_updates", 919 + "chatIdHint": "Введите ID чата Telegram или имя пользователя канала, куда будут отправляться обновления.", 920 + "threadIdLabel": "ID темы", 921 + "threadIdPlaceholder": "Необязательный ID темы", 922 + "threadIdHint": "Необязательно. Используйте для форумных тем внутри Telegram-групп.", 923 + "chatLabelLabel": "Метка чата", 924 + "chatLabelPlaceholder": "Обновления разработки", 925 + "chatLabelHint": "Необязательная метка для справки внутри Kaneo.", 926 + "eventsTitle": "Уведомления о событиях", 927 + "eventsHint": "Выберите, какие изменения проекта отправлять в Telegram.", 928 + "events": { 929 + "taskCreated": "Новые задачи", 930 + "taskStatusChanged": "Изменения статуса", 931 + "taskPriorityChanged": "Изменения приоритета", 932 + "taskTitleChanged": "Изменения названия", 933 + "taskDescriptionChanged": "Изменения описания", 934 + "taskCommentCreated": "Новые комментарии" 935 + }, 936 + "connect": "Подключить Telegram", 937 + "saveChanges": "Сохранить изменения", 938 + "disconnect": "Отключить" 939 + }, 940 + "repositoryBrowser": { 941 + "title": "Выберите репозиторий", 942 + "description": "Выберите репозиторий, на котором установлен ваш GitHub App, для включения синхронизации issues.", 943 + "searchPlaceholder": "Поиск репозиториев...", 944 + "loadError": "Не удалось загрузить репозитории", 945 + "tryAgain": "Попробовать снова", 946 + "emptyTitle": "Репозитории не найдены", 947 + "emptyHint": "Установите GitHub App на ваши репозитории, чтобы они появились здесь.", 948 + "installGithubApp": "Установить GitHub App", 949 + "noSearchMatchTitle": "Репозитории по вашему запросу не найдены", 950 + "noSearchMatchHint": "Попробуйте изменить поисковый запрос или очистите поиск, чтобы увидеть все репозитории.", 951 + "footerSummary": "{{repoCount}} репозиториев в {{installationCount}} установках", 952 + "manageInstallations": "Управление установками", 953 + "updatedPrefix": "Обновлён", 954 + "relativeJustNow": "только что", 955 + "relativeMinutesAgo": "{{count}} мин. назад", 956 + "relativeHoursAgo": "{{count}} ч. назад", 957 + "relativeDaysAgo": "{{count}} дн. назад" 958 + }, 959 + "tasksImportExport": { 960 + "exportTasks": "Экспорт задач", 961 + "importTasks": "Импорт задач", 962 + "dialogTitle": "Импорт задач", 963 + "dialogDescription": "Загрузите JSON-файл с задачами для импорта в этот проект.", 964 + "expectedFormat": "Ожидаемый формат:", 965 + "dropHint": "Перетащите JSON-файл сюда", 966 + "selectFile": "Выбрать файл", 967 + "exporting": "Экспорт задач...", 968 + "exportSuccess": "Задачи успешно экспортированы", 969 + "exportError": "Не удалось экспортировать задачи", 970 + "importing": "Импорт задач...", 971 + "importSuccess_one": "Успешно импортирована {{count}} задача", 972 + "importSuccess_few": "Успешно импортировано {{count}} задачи", 973 + "importSuccess_many": "Успешно импортировано {{count}} задач", 974 + "importSuccess_other": "Успешно импортировано {{count}} задач", 975 + "importPartialError_one": "Не удалось импортировать {{count}} задачу", 976 + "importPartialError_few": "Не удалось импортировать {{count}} задачи", 977 + "importPartialError_many": "Не удалось импортировать {{count}} задач", 978 + "importPartialError_other": "Не удалось импортировать {{count}} задач", 979 + "importError": "Не удалось импортировать задачи", 980 + "invalidFormat": "Некорректный формат файла импорта", 981 + "noFileDropped": "Файл не был перетащен", 982 + "notJsonFile": "Пожалуйста, загрузите JSON-файл" 983 + }, 984 + "workflowEditor": { 985 + "loading": "Загрузка...", 986 + "createColumnsFirst": "Сначала создайте колонки для настройки правил автоматизации.", 987 + "githubHeading": "GitHub", 988 + "githubHint": "Когда происходит событие GitHub, перемещать связанную задачу в колонку.", 989 + "giteaHeading": "Gitea", 990 + "giteaHint": "Когда происходит событие вебхука Gitea, перемещать связанную задачу в колонку.", 991 + "selectColumnPlaceholder": "Выберите колонку...", 992 + "toastUpdated": "Правило рабочего процесса обновлено", 993 + "toastError": "Не удалось обновить правило", 994 + "events": { 995 + "branch_push": "Push ветки", 996 + "pr_opened": "PR открыт", 997 + "pr_merged": "PR влит", 998 + "issue_opened": "Issue открыт", 999 + "issue_closed": "Issue закрыт" 1000 + } 1001 + }, 1002 + "externalLinks": { 1003 + "resources": "Ресурсы", 1004 + "issue": "Issue", 1005 + "branch": "Ветка", 1006 + "merged": "Влит", 1007 + "draft": "Черновик", 1008 + "open": "Открыт" 1009 + } 1010 + }, 1011 + "navigation": { 1012 + "commandPalette": { 1013 + "suggestions": "Предложения", 1014 + "commands": "Команды", 1015 + "projects": "Проекты", 1016 + "search": "Поиск", 1017 + "members": "Участники", 1018 + "createTask": "Создать задачу", 1019 + "createProject": "Создать проект", 1020 + "createWorkspace": "Создать рабочую область", 1021 + "lightTheme": "Светлая тема", 1022 + "darkTheme": "Тёмная тема", 1023 + "systemTheme": "Системная тема", 1024 + "keyboardShortcuts": "Сочетания клавиш", 1025 + "inputPlaceholder": "Поиск приложений и команд...", 1026 + "empty": "Результатов не найдено.", 1027 + "footer": { 1028 + "navigate": "Навигация", 1029 + "open": "Открыть", 1030 + "close": "Закрыть" 1031 + } 1032 + }, 1033 + "notifications": "Уведомления", 1034 + "sidebar": { 1035 + "overview": "Обзор", 1036 + "projects": "Проекты", 1037 + "members": "Участники", 1038 + "invitations": "Приглашения", 1039 + "more": "Ещё" 1040 + }, 1041 + "projectList": { 1042 + "viewProject": "Открыть проект", 1043 + "shareProject": "Поделиться проектом", 1044 + "projectSettings": "Настройки проекта", 1045 + "linkCopied": "Ссылка на проект скопирована в буфер обмена", 1046 + "addProject": "Добавить проект", 1047 + "deleteConfirmTitle": "Удалить проект?", 1048 + "deleteConfirmDescription": "Проект и все его данные будут безвозвратно удалены. Это действие нельзя отменить.", 1049 + "deletedToast": "Проект удалён", 1050 + "deleteProject": "Удалить проект" 1051 + }, 1052 + "search": { 1053 + "inputPlaceholder": "Поиск задач, проектов, комментариев...", 1054 + "minCharsHint": "Введите минимум 3 символа для поиска", 1055 + "groups": { 1056 + "task": "Задачи", 1057 + "project": "Проекты", 1058 + "workspace": "Рабочие области", 1059 + "comment": "Комментарии", 1060 + "activity": "Активности", 1061 + "fallback": "Результаты" 1062 + } 1063 + }, 1064 + "settingsLayout": { 1065 + "toggleSidebar": "Переключить боковую панель", 1066 + "back": "Назад" 1067 + }, 1068 + "userMenu": { 1069 + "signedOutSuccess": "Выход выполнен успешно", 1070 + "signOutFailed": "Не удалось выйти", 1071 + "unnamedUser": "Пользователь", 1072 + "settings": "Настройки", 1073 + "signingOut": "Выход...", 1074 + "logOut": "Выйти" 1075 + }, 1076 + "workspaceSwitcher": { 1077 + "workspaces": "Рабочие области", 1078 + "switching": "Переключение...", 1079 + "addWorkspace": "Добавить рабочую область", 1080 + "selectWorkspace": "Выберите рабочую область" 1081 + }, 1082 + "page": { 1083 + "projectsTitle": "Проекты", 1084 + "settingsTitle": "Настройки", 1085 + "backToWorkspace": "Вернуться к рабочей области", 1086 + "settingsWorkspaceTab": "Рабочая область" 1087 + }, 1088 + "projectSettings": { 1089 + "projectLabel": "Проект" 1090 + }, 1091 + "keyboardShortcuts": { 1092 + "title": "Сочетания клавиш", 1093 + "subtitle": "Ускорьте работу с помощью сочетаний клавиш", 1094 + "searchPlaceholder": "Поиск сочетаний...", 1095 + "footer": "Нажмите <kbd>Escape</kbd>, чтобы закрыть", 1096 + "categories": { 1097 + "general": "Основные", 1098 + "create": "Создание", 1099 + "views": "Представления", 1100 + "navigation": "Навигация", 1101 + "quickSelect": "Быстрый выбор (в выпадающих списках)" 1102 + }, 1103 + "items": { 1104 + "openCommandPalette": "Открыть палитру команд", 1105 + "globalSearch": "Глобальный поиск", 1106 + "toggleSidebar": "Переключить боковую панель", 1107 + "showShortcuts": "Показать сочетания клавиш", 1108 + "closeModal": "Закрыть модальное окно/всплывающее меню", 1109 + "createTask": "Создать задачу", 1110 + "createProject": "Создать проект", 1111 + "createWorkspace": "Создать рабочую область", 1112 + "boardView": "Переключиться на доску", 1113 + "listView": "Переключиться на список", 1114 + "backlogView": "Переключиться на бэклог", 1115 + "nextTask": "Следующая задача", 1116 + "prevTask": "Предыдущая задача", 1117 + "openTask": "Открыть выбранную задачу", 1118 + "quickSelectNumber": "Выбрать вариант по номеру" 1119 + } 1120 + } 1121 + }, 1122 + "notifications": { 1123 + "title": "Уведомления", 1124 + "newCount_one": "{{count}} новое", 1125 + "newCount_few": "{{count}} новых", 1126 + "newCount_many": "{{count}} новых", 1127 + "newCount_other": "{{count}} новых", 1128 + "emptyTitle": "Уведомлений пока нет", 1129 + "emptySubtitle": "Здесь будут отображаться обновления и активность.", 1130 + "clearAll": "Очистить все уведомления", 1131 + "clearDialogTitle": "Очистить все уведомления?", 1132 + "clearDialogDescription": "Все уведомления будут безвозвратно удалены. Это действие нельзя отменить.", 1133 + "shortcuts": { 1134 + "open": "Открыть уведомления" 1135 + }, 1136 + "events": { 1137 + "task_created": { 1138 + "title": "Создана новая задача", 1139 + "content": "Задача «{{taskTitle}}» создана" 1140 + }, 1141 + "workspace_created": { 1142 + "title": "Рабочая область создана", 1143 + "content": "Ваша рабочая область «{{workspaceName}}» успешно создана" 1144 + }, 1145 + "task_status_changed": { 1146 + "title": "Статус задачи изменён", 1147 + "content": "Статус задачи «{{taskTitle}}» изменён с «{{oldStatus}}» на «{{newStatus}}»" 1148 + }, 1149 + "task_assignee_changed": { 1150 + "title": "Задача назначена вам", 1151 + "content": "Вам назначена задача: {{taskTitle}}" 1152 + }, 1153 + "time_entry_created": { 1154 + "title": "Трекинг времени начат", 1155 + "contentWithTask": "Трекинг времени начат для задачи: {{taskTitle}}", 1156 + "contentWithoutTask": "Трекинг времени начат для задачи" 1157 + } 1158 + } 1159 + }, 1160 + "activity": { 1161 + "assignedToSelf": "назначил задачу себе", 1162 + "unassigned": "снял назначение с задачи", 1163 + "assignedTo": "назначил задачу на {{name}}", 1164 + "changedStatus": "изменил статус с {{from}} на {{to}}", 1165 + "changedPriority": "изменил приоритет с {{from}} на {{to}}", 1166 + "clearedDueDate": "очистил срок выполнения", 1167 + "setDueDate": "установил срок выполнения: {{date}}", 1168 + "changedDueDate": "изменил срок выполнения с {{from}} на {{to}}", 1169 + "changedTitle": "изменил название с «{{from}}» на «{{to}}»", 1170 + "githubUser": "Пользователь GitHub", 1171 + "comment": { 1172 + "github": "GitHub", 1173 + "viewGithubProfile": "Открыть профиль GitHub", 1174 + "commentedOnGithub": "прокомментировал на GitHub", 1175 + "cannotBeEmpty": "Комментарий не может быть пустым", 1176 + "mustBeLoggedInToEdit": "Для редактирования комментариев необходимо войти в систему", 1177 + "updated": "Комментарий обновлён", 1178 + "failedToUpdate": "Не удалось обновить комментарий", 1179 + "edit": "Редактировать комментарий", 1180 + "editPlaceholder": "Редактировать комментарий...", 1181 + "save": "Сохранить", 1182 + "added": "Комментарий добавлен", 1183 + "failedToAdd": "Не удалось добавить комментарий", 1184 + "leavePlaceholder": "Оставить комментарий...", 1185 + "attachFile": "Прикрепить файл", 1186 + "submitShortcut": "Отправить комментарий", 1187 + "editor": { 1188 + "uploadsOnlyOnSavedTasks": "Загрузка файлов доступна только для сохранённых задач.", 1189 + "uploadingFile": "Загрузка файла...", 1190 + "imageUploaded": "Изображение загружено", 1191 + "fileAttached": "Файл прикреплён", 1192 + "failedToUploadFile": "Не удалось загрузить файл", 1193 + "enterUrl": "Введите URL", 1194 + "plaintext": "Простой текст", 1195 + "autoDetect": "Автоопределение", 1196 + "slashGroupText": "Текст", 1197 + "slashGroupLists": "Списки", 1198 + "slashGroupInsert": "Вставка", 1199 + "slashParagraph": "Текст", 1200 + "slashHeading": "Заголовок", 1201 + "slashBulletList": "Маркированный список", 1202 + "slashTaskList": "Чек-лист", 1203 + "slashOrderedList": "Нумерованный список", 1204 + "slashQuote": "Цитата", 1205 + "slashCodeBlock": "Блок кода", 1206 + "slashTable": "Таблица", 1207 + "slashFile": "Файл", 1208 + "searchParagraph": "текст параграф обычный", 1209 + "searchHeading": "заголовок h2", 1210 + "searchBulletList": "список маркированный", 1211 + "searchTaskList": "чеклист список задач", 1212 + "searchOrderedList": "список нумерованный", 1213 + "searchQuote": "цитата блок", 1214 + "searchCodeBlock": "код фрагмент", 1215 + "searchTable": "таблица сетка", 1216 + "searchFile": "файл вложение изображение фото загрузка", 1217 + "embedErrorInvalidUrl": "Введите корректный URL", 1218 + "embedErrorYoutubeOnly": "Можно встраивать только ссылки YouTube.", 1219 + "embedVideo": "Встроить видео", 1220 + "keepAsLink": "Оставить как ссылку", 1221 + "hintTab": "Tab", 1222 + "hintEsc": "Esc", 1223 + "pasteUrl": "Вставьте URL", 1224 + "asLink": "Как ссылку", 1225 + "embed": "Встроить", 1226 + "noCommands": "Нет команд", 1227 + "ariaCommentContent": "Содержимое комментария", 1228 + "ariaCommentEditor": "Редактор комментариев", 1229 + "ariaCopyCode": "Скопировать код", 1230 + "ariaCopied": "Скопировано", 1231 + "copy": "Копировать", 1232 + "copied": "Скопировано", 1233 + "dropImageToUpload": "Перетащите изображение для загрузки", 1234 + "previewImageAlt": "Предпросмотр изображения", 1235 + "codeLang": { 1236 + "bash": "Bash", 1237 + "csharp": "C#", 1238 + "cpp": "C++", 1239 + "css": "CSS", 1240 + "go": "Golang", 1241 + "graphql": "GraphQL", 1242 + "html": "HTML", 1243 + "json": "JSON", 1244 + "java": "Java", 1245 + "javascript": "JavaScript", 1246 + "markdown": "Markdown", 1247 + "plaintext": "Простой текст", 1248 + "python": "Python", 1249 + "rust": "Rust", 1250 + "sql": "SQL", 1251 + "swift": "Swift", 1252 + "typescript": "TypeScript", 1253 + "yaml": "YAML" 1254 + } 1255 + } 1256 + } 1257 + }, 1258 + "tasks": { 1259 + "status": { 1260 + "label": "Статус", 1261 + "to-do": "К выполнению", 1262 + "in-progress": "В работе", 1263 + "in-review": "На проверке", 1264 + "done": "Готово", 1265 + "archived": "В архиве", 1266 + "planned": "Запланировано" 1267 + }, 1268 + "priority": { 1269 + "label": "Приоритет", 1270 + "no-priority": "Без приоритета", 1271 + "low": "Низкий", 1272 + "medium": "Средний", 1273 + "high": "Высокий", 1274 + "urgent": "Срочный" 1275 + }, 1276 + "boardSearchPlaceholder": "Поиск задач...", 1277 + "view": { 1278 + "board": "Доска", 1279 + "list": "Список" 1280 + }, 1281 + "common": { 1282 + "selectTask": "Выбрать задачу", 1283 + "loadingTask": "Загрузка задачи..." 1284 + }, 1285 + "detail": { 1286 + "subtaskOf": "Подзадача", 1287 + "activity": "Активность", 1288 + "noActivity": "Активность не найдена", 1289 + "openInFullPage": "Открыть на полной странице", 1290 + "titlePlaceholder": "Нажмите, чтобы добавить название", 1291 + "addDescription": "Добавить описание...", 1292 + "editor": { 1293 + "ariaLabel": "Редактор описания задачи", 1294 + "placeholder": "Напишите описание...", 1295 + "previewImage": "Предпросмотр изображения", 1296 + "enterUrl": "Введите URL", 1297 + "autoDetect": "Автоопределение", 1298 + "copyCode": "Скопировать код", 1299 + "copy": "Копировать", 1300 + "copied": "Скопировано", 1301 + "attachFile": "Прикрепить файл", 1302 + "dropToUpload": "Перетащите изображение для загрузки", 1303 + "checkbox": { 1304 + "markIncomplete": "Отметить задачу как невыполненную", 1305 + "markComplete": "Отметить задачу как выполненную" 1306 + }, 1307 + "upload": { 1308 + "loading": "Загрузка файла...", 1309 + "failed": "Не удалось загрузить файл", 1310 + "imageSuccess": "Изображение загружено", 1311 + "fileSuccess": "Файл прикреплён" 1312 + }, 1313 + "slash": { 1314 + "groups": { 1315 + "text": "Текст", 1316 + "lists": "Списки", 1317 + "insert": "Вставка" 1318 + }, 1319 + "empty": "Нет команд", 1320 + "commands": { 1321 + "paragraph": "Текст", 1322 + "heading-2": "Заголовок", 1323 + "bullet-list": "Маркированный список", 1324 + "task-list": "Чек-лист", 1325 + "ordered-list": "Нумерованный список", 1326 + "blockquote": "Цитата", 1327 + "code-block": "Блок кода", 1328 + "table": "Таблица", 1329 + "file": "Файл" 1330 + } 1331 + }, 1332 + "languages": { 1333 + "bash": "Bash", 1334 + "csharp": "C#", 1335 + "cpp": "C++", 1336 + "css": "CSS", 1337 + "clojure": "Clojure", 1338 + "cypher": "Cypher", 1339 + "dart": "Dart", 1340 + "diff": "Diff", 1341 + "elixir": "Elixir", 1342 + "excel": "Excel", 1343 + "go": "Golang", 1344 + "graphql": "GraphQL", 1345 + "html": "HTML", 1346 + "haskell": "Haskell", 1347 + "json": "JSON", 1348 + "java": "Java", 1349 + "javascript": "JavaScript", 1350 + "kotlin": "Kotlin", 1351 + "makefile": "Makefile", 1352 + "markdown": "Markdown", 1353 + "ocaml": "OCaml", 1354 + "php": "PHP", 1355 + "perl": "Perl", 1356 + "plaintext": "Простой текст", 1357 + "python": "Python", 1358 + "r": "R", 1359 + "reasonml": "ReasonML", 1360 + "ruby": "Ruby", 1361 + "rust": "Rust", 1362 + "sql": "SQL", 1363 + "swift": "Swift", 1364 + "toml": "TOML", 1365 + "terraform": "Terraform", 1366 + "typescript": "TypeScript", 1367 + "xml": "XML", 1368 + "yaml": "YAML" 1369 + }, 1370 + "embed": { 1371 + "choice": { 1372 + "embedVideo": "Встроить видео", 1373 + "keepAsLink": "Оставить как ссылку" 1374 + }, 1375 + "inputPlaceholder": "Вставьте URL", 1376 + "embeddedContent": "Встроенный контент", 1377 + "asLink": "Как ссылку", 1378 + "submit": "Встроить", 1379 + "errors": { 1380 + "invalidUrl": "Введите корректный URL", 1381 + "onlyYoutube": "Можно встраивать только ссылки YouTube." 1382 + }, 1383 + "onlyYoutubeInline": "Можно встраивать только URL YouTube. Используйте режим ссылки." 1384 + } 1385 + } 1386 + }, 1387 + "entity": { 1388 + "task": "Задача" 1389 + }, 1390 + "relations": { 1391 + "title": "Связи", 1392 + "tasksInProject": "Задачи в проекте", 1393 + "linkError": "Не удалось связать задачу", 1394 + "empty": "Нет связанных задач", 1395 + "searchPlaceholder": "Поиск задач для связывания...", 1396 + "noTasksFound": "Задачи не найдены", 1397 + "openTask": "Открыть задачу", 1398 + "removeRelation": "Удалить связь", 1399 + "related": "Связана", 1400 + "blocks": "Блокирует", 1401 + "selectTask": "Выберите задачу для связывания", 1402 + "types": { 1403 + "blocks": "блокирует", 1404 + "related": "связана с" 1405 + } 1406 + }, 1407 + "subtasks": { 1408 + "title": "Подзадачи", 1409 + "inputPlaceholder": "Название подзадачи...", 1410 + "addAction": "Добавить", 1411 + "empty": "Подзадач пока нет", 1412 + "createError": "Не удалось создать подзадачу", 1413 + "deleteSuccess": "Задача успешно удалена", 1414 + "deleteError": "Не удалось удалить задачу", 1415 + "deleteDialogTitle": "Удалить задачу?", 1416 + "deleteDialogDescription": "Задача и все её данные будут безвозвратно удалены. Это действие нельзя отменить.", 1417 + "deleteAction": "Удалить задачу" 1418 + }, 1419 + "properties": { 1420 + "title": "Свойства", 1421 + "labels": "Метки", 1422 + "copyTaskLink": "Скопировать ссылку на задачу", 1423 + "copyTaskBranch": "Скопировать ветку задачи", 1424 + "start": "Начало", 1425 + "startDate": "Дата начала", 1426 + "noDate": "Без даты" 1427 + }, 1428 + "move": { 1429 + "title": "Переместить задачу", 1430 + "projectLabel": "Целевой проект", 1431 + "projectPlaceholder": "Выберите проект", 1432 + "statusLabel": "Целевой статус", 1433 + "statusHintKeep": "Рабочий процесс этого проекта уже поддерживает текущий статус.", 1434 + "statusHintAdjust": "Выберите статус для использования в целевом проекте.", 1435 + "action": "Переместить задачу", 1436 + "success": "Задача успешно перемещена", 1437 + "error": "Не удалось переместить задачу" 1438 + }, 1439 + "popover": { 1440 + "assignee": { 1441 + "unassigned": "Не назначено", 1442 + "updateError": "Не удалось обновить исполнителя задачи" 1443 + }, 1444 + "status": { 1445 + "updateError": "Не удалось обновить статус задачи" 1446 + }, 1447 + "priority": { 1448 + "updateError": "Не удалось обновить приоритет задачи" 1449 + }, 1450 + "dueDate": { 1451 + "updateSuccess": "Срок выполнения задачи успешно обновлён", 1452 + "updateError": "Не удалось обновить срок выполнения задачи", 1453 + "clear": "Очистить дату" 1454 + }, 1455 + "startDate": { 1456 + "updateSuccess": "Дата начала задачи успешно обновлена", 1457 + "updateError": "Не удалось обновить дату начала задачи", 1458 + "clear": "Очистить дату начала" 1459 + }, 1460 + "labels": { 1461 + "searchPlaceholder": "Поиск меток...", 1462 + "empty": "Метки не найдены", 1463 + "create": "Создать «{{name}}»", 1464 + "chooseColor": "Выберите цвет", 1465 + "addSuccess": "Метка добавлена", 1466 + "removeSuccess": "Метка удалена", 1467 + "updateError": "Не удалось обновить метку", 1468 + "createSuccess": "Метка создана и добавлена", 1469 + "createError": "Не удалось создать метку", 1470 + "colors": { 1471 + "stone": "Камень", 1472 + "slate": "Сланец", 1473 + "lavender": "Лаванда", 1474 + "sage": "Шалфей", 1475 + "forest": "Лес", 1476 + "amber": "Янтарь", 1477 + "terracotta": "Терракота", 1478 + "rose": "Роза", 1479 + "crimson": "Малиновый" 1480 + } 1481 + } 1482 + }, 1483 + "backlog": { 1484 + "pageTitle": "Бэклог — {{name}}", 1485 + "noTasksToMove": "Нет запланированных задач для перемещения", 1486 + "moveAllConfirm": "Переместить все {{count}} запланированных задач в «К выполнению»?", 1487 + "moveAllSuccess_one": "{{count}} задача перемещена в «К выполнению»", 1488 + "moveAllSuccess_few": "{{count}} задачи перемещены в «К выполнению»", 1489 + "moveAllSuccess_many": "{{count}} задач перемещено в «К выполнению»", 1490 + "moveAllSuccess_other": "{{count}} задач перемещено в «К выполнению»", 1491 + "plan": "Запланировать", 1492 + "moveAllTooltip": "Переместить все запланированные в «К выполнению»", 1493 + "moveAll": "Переместить все", 1494 + "addTask": "Добавить задачу", 1495 + "filter": "Фильтр", 1496 + "addFilter": "Добавить фильтр...", 1497 + "sections": { 1498 + "planned": "Запланировано", 1499 + "archived": "В архиве" 1500 + }, 1501 + "noTasksInSection": "Нет задач в разделе «{{section}}»", 1502 + "filters": { 1503 + "priority": "Приоритет: {{name}}", 1504 + "assignee": "Исполнитель: {{name}}", 1505 + "due": "Срок: {{date}}", 1506 + "label": "Метка: {{name}}", 1507 + "dueThisWeek": "Срок на этой неделе", 1508 + "dueNextWeek": "Срок на следующей неделе", 1509 + "noDueDate": "Без срока" 1510 + } 1511 + }, 1512 + "sort": { 1513 + "label": "Сортировка", 1514 + "by": "Сортировать по", 1515 + "direction": "Направление", 1516 + "ascending": "По возрастанию", 1517 + "descending": "По убыванию", 1518 + "fields": { 1519 + "position": "Вручную (позиция)", 1520 + "createdAt": "Дата создания", 1521 + "priority": "Приоритет", 1522 + "dueDate": "Срок выполнения", 1523 + "title": "Название", 1524 + "number": "Номер задачи" 1525 + } 1526 + }, 1527 + "boardFilters": { 1528 + "filterBy": "Фильтровать по", 1529 + "allStatuses": "Все статусы", 1530 + "allPriorities": "Все приоритеты", 1531 + "allAssignees": "Все исполнители", 1532 + "allDueDates": "Все сроки", 1533 + "allLabels": "Все метки", 1534 + "selectedCount_one": "{{count}} выбран", 1535 + "selectedCount_few": "{{count}} выбрано", 1536 + "selectedCount_many": "{{count}} выбрано", 1537 + "selectedCount_other": "{{count}} выбрано", 1538 + "subjects": { 1539 + "status": "Статус", 1540 + "priority": "Приоритет", 1541 + "assignee": "Исполнитель", 1542 + "dueDate": "Срок выполнения", 1543 + "labels": "Метки" 1544 + }, 1545 + "operators": { 1546 + "isAnyOf": "любой из", 1547 + "includeAnyOf": "включает любой из" 1548 + } 1549 + }, 1550 + "gantt": { 1551 + "pageTitle": "{{name}} — Гант", 1552 + "title": "Временная шкала Ганта", 1553 + "searchPlaceholder": "Поиск запланированных задач...", 1554 + "hideTasks": "Скрыть задачи", 1555 + "showTasks": "Показать задачи", 1556 + "noTasks": "Нет запланированных задач", 1557 + "noTasksSubtitle": "Добавьте дату начала, срок выполнения или оба значения к задачам, чтобы разместить их на временной шкале проекта.", 1558 + "noTasksFound": "Задачи не найдены", 1559 + "noTasksMatch": "Нет запланированных задач, соответствующих «{{query}}»", 1560 + "taskHeader": "Задача", 1561 + "updateDatesError": "Не удалось обновить даты задачи", 1562 + "resizeStart": "Изменить дату начала", 1563 + "resizeDue": "Изменить срок выполнения", 1564 + "taskAriaLabel": "{{title}} — откройте или перетащите для перемещения" 1565 + }, 1566 + "delete": { 1567 + "title": "Удалить задачу?", 1568 + "description": "Задача и все её данные будут безвозвратно удалены. Это действие нельзя отменить.", 1569 + "action": "Удалить задачу", 1570 + "success": "Задача успешно удалена", 1571 + "error": "Не удалось удалить задачу" 1572 + }, 1573 + "archive": { 1574 + "success_one": "{{count}} задача архивирована", 1575 + "success_few": "{{count}} задачи архивированы", 1576 + "success_many": "{{count}} задач архивировано", 1577 + "success_other": "{{count}} задач архивировано" 1578 + }, 1579 + "listView": { 1580 + "addTask": "Добавить задачу", 1581 + "archiveAllTooltip": "Архивировать все завершённые задачи", 1582 + "noTasks": "Нет задач" 1583 + }, 1584 + "kanban": { 1585 + "addTask": "Добавить задачу" 1586 + }, 1587 + "pr": { 1588 + "merged": "Влит", 1589 + "draft": "Черновик", 1590 + "open": "Открыт", 1591 + "label": "Pull Request", 1592 + "count_one": "{{count}} PR", 1593 + "count_few": "{{count}} PR", 1594 + "count_many": "{{count}} PR", 1595 + "count_other": "{{count}} PR" 1596 + }, 1597 + "assignee": { 1598 + "label": "Исполнитель", 1599 + "unassigned": "Не назначено" 1600 + }, 1601 + "dueDate": { 1602 + "label": "Срок выполнения", 1603 + "clear": "Очистить дату", 1604 + "updateSuccess": "Срок выполнения задачи успешно обновлён", 1605 + "updateError": "Не удалось обновить срок выполнения задачи", 1606 + "clearSuccess": "Срок выполнения задачи очищен", 1607 + "clearError": "Не удалось очистить срок выполнения" 1608 + }, 1609 + "labels": { 1610 + "label": "Метки", 1611 + "empty": "Нет доступных меток" 1612 + }, 1613 + "update": { 1614 + "success": "Задача успешно обновлена", 1615 + "error": "Не удалось обновить задачу" 1616 + }, 1617 + "contextMenu": { 1618 + "copyLink": "Скопировать ссылку", 1619 + "copyLinkSuccess": "Ссылка на задачу скопирована!" 1620 + }, 1621 + "actions": { 1622 + "archive": "Архивировать", 1623 + "markAsPlanned": "Отметить как запланированную", 1624 + "delete": "Удалить..." 1625 + }, 1626 + "bulk": { 1627 + "selectedCount_one": "{{count}} выбрана", 1628 + "selectedCount_few": "{{count}} выбраны", 1629 + "selectedCount_many": "{{count}} выбрано", 1630 + "selectedCount_other": "{{count}} выбрано", 1631 + "moveToBacklog": "Переместить в бэклог", 1632 + "moveToBacklogSuccess_one": "{{count}} задача перемещена в бэклог", 1633 + "moveToBacklogSuccess_few": "{{count}} задачи перемещены в бэклог", 1634 + "moveToBacklogSuccess_many": "{{count}} задач перемещено в бэклог", 1635 + "moveToBacklogSuccess_other": "{{count}} задач перемещено в бэклог", 1636 + "moveToBacklogError": "Не удалось переместить задачи в бэклог", 1637 + "moveToBoard": "Переместить на доску", 1638 + "moveToBoardSuccess_one": "{{count}} задача перемещена на доску", 1639 + "moveToBoardSuccess_few": "{{count}} задачи перемещены на доску", 1640 + "moveToBoardSuccess_many": "{{count}} задач перемещено на доску", 1641 + "moveToBoardSuccess_other": "{{count}} задач перемещено на доску", 1642 + "moveToBoardError": "Не удалось переместить задачи на доску", 1643 + "delete": "Удалить задачи", 1644 + "deleteConfirm_one": "Удалить {{count}} задачу? Это действие нельзя отменить.", 1645 + "deleteConfirm_few": "Удалить {{count}} задачи? Это действие нельзя отменить.", 1646 + "deleteConfirm_many": "Удалить {{count}} задач? Это действие нельзя отменить.", 1647 + "deleteConfirm_other": "Удалить {{count}} задач? Это действие нельзя отменить.", 1648 + "deleteSuccess_one": "{{count}} задача удалена", 1649 + "deleteSuccess_few": "{{count}} задачи удалены", 1650 + "deleteSuccess_many": "{{count}} задач удалено", 1651 + "deleteSuccess_other": "{{count}} задач удалено", 1652 + "deleteError": "Не удалось удалить задачи", 1653 + "archive": "Архивировать задачи", 1654 + "archiveSuccess_one": "{{count}} задача архивирована", 1655 + "archiveSuccess_few": "{{count}} задачи архивированы", 1656 + "archiveSuccess_many": "{{count}} задач архивировано", 1657 + "archiveSuccess_other": "{{count}} задач архивировано", 1658 + "archiveError": "Не удалось архивировать задачи", 1659 + "updateSuccess_one": "{{count}} задача обновлена", 1660 + "updateSuccess_few": "{{count}} задачи обновлены", 1661 + "updateSuccess_many": "{{count}} задач обновлено", 1662 + "updateSuccess_other": "{{count}} задач обновлено", 1663 + "updateError": "Не удалось обновить задачи", 1664 + "assignTo": "Назначить на", 1665 + "assignSuccess_one": "{{count}} задача назначена", 1666 + "assignSuccess_few": "{{count}} задачи назначены", 1667 + "assignSuccess_many": "{{count}} задач назначено", 1668 + "assignSuccess_other": "{{count}} задач назначено", 1669 + "assignError": "Не удалось назначить задачи", 1670 + "setPriority": "Установить приоритет", 1671 + "updatePriorityError": "Не удалось обновить приоритет", 1672 + "addLabel": "Добавить метку", 1673 + "addLabelSuccess_one": "Метка добавлена к {{count}} задаче", 1674 + "addLabelSuccess_few": "Метка добавлена к {{count}} задачам", 1675 + "addLabelSuccess_many": "Метка добавлена к {{count}} задачам", 1676 + "addLabelSuccess_other": "Метка добавлена к {{count}} задачам", 1677 + "addLabelError": "Не удалось добавить метку", 1678 + "setDueDate": "Установить срок", 1679 + "updateDueDateError": "Не удалось обновить срок выполнения", 1680 + "actions": "Действия", 1681 + "searchActions": "Поиск действий...", 1682 + "noActionsFound": "Действия не найдены.", 1683 + "changeStatus": "Изменить статус" 1684 + } 1685 + }, 1686 + "invitations": { 1687 + "pageTitle": "Приглашения", 1688 + "pendingInvitations": "Ожидающие приглашения", 1689 + "acceptSubtitle": "Примите приглашения для присоединения к рабочим областям", 1690 + "noPendingTitle": "Нет ожидающих приглашений", 1691 + "noPendingDescription": "На данный момент у вас нет ожидающих приглашений в рабочие области.", 1692 + "continueToSetup": "Продолжить настройку", 1693 + "skipForNow": "Пропустить пока", 1694 + "table": { 1695 + "workspace": "Рабочая область", 1696 + "invitedBy": "Пригласил", 1697 + "expires": "Истекает" 1698 + }, 1699 + "toast": { 1700 + "acceptError": "Не удалось принять приглашение", 1701 + "acceptSuccess": "Приглашение принято! Добро пожаловать в команду.", 1702 + "rejectError": "Не удалось отклонить приглашение", 1703 + "rejectSuccess": "Приглашение отклонено" 1704 + } 1705 + }, 1706 + "workspace": { 1707 + "projects": { 1708 + "pageTitle": "Проекты", 1709 + "createProject": "Создать проект", 1710 + "title": "Название", 1711 + "progress": "Прогресс", 1712 + "targetDate": "Целевая дата", 1713 + "dueDate": "Срок выполнения", 1714 + "status": "Статус", 1715 + "emptyTitle": "Проектов пока нет", 1716 + "emptyDescription": "Начните с создания вашего первого проекта.", 1717 + "projectStatus": { 1718 + "notStarted": "Не начат", 1719 + "complete": "Завершён", 1720 + "inProgress": "В работе" 1721 + }, 1722 + "noDueDate": "Без срока" 1723 + }, 1724 + "search": { 1725 + "pageTitle": "Поиск", 1726 + "backToDashboard": "Вернуться к панели управления", 1727 + "placeholder": "Поиск задач по названию или короткому ID (например, DEP-23)...", 1728 + "hint": "Поиск по всем проектам в этой рабочей области. Используйте короткие ID, такие как DEP-23, чтобы найти конкретные задачи.", 1729 + "searching": "Поиск...", 1730 + "resultsFound_one": "Найден {{count}} результат", 1731 + "resultsFound_few": "Найдено {{count}} результата", 1732 + "resultsFound_many": "Найдено {{count}} результатов", 1733 + "resultsFound_other": "Найдено {{count}} результатов", 1734 + "noResultsTitle": "Результатов не найдено", 1735 + "noResultsDescription": "Попробуйте изменить поисковый запрос или поискать что-то другое", 1736 + "startTitle": "Начните поиск", 1737 + "startDescription": "Введите поисковый запрос для поиска задач по всем проектам", 1738 + "quickSearchesLabel": "Быстрый поиск:", 1739 + "suggestionHighPriority": "Высокий приоритет", 1740 + "suggestionBug": "Баг", 1741 + "suggestionFeature": "Фича", 1742 + "suggestionInProgress": "В работе", 1743 + "suggestionCompleted": "Завершённые" 1744 + }, 1745 + "create": { 1746 + "pageTitle": "Создание рабочей области", 1747 + "heading": "Создать новую рабочую область", 1748 + "subtitle": "Рабочие области — это общие среды, где команды могут работать над проектами, циклами и задачами.", 1749 + "nameLabel": "Название рабочей области", 1750 + "namePlaceholder": "Введите название рабочей области", 1751 + "descriptionLabel": "Описание (необязательно)", 1752 + "descriptionPlaceholder": "Добавьте описание для вашей рабочей области", 1753 + "required": "Обязательно", 1754 + "creating": "Создание...", 1755 + "submit": "Создать рабочую область", 1756 + "success": "Рабочая область успешно создана", 1757 + "error": "Не удалось создать рабочую область" 1758 + } 1759 + }, 1760 + "team": { 1761 + "roles": { 1762 + "owner": "Владелец", 1763 + "admin": "Администратор", 1764 + "member": "Участник" 1765 + }, 1766 + "members": { 1767 + "pageTitle": "Участники", 1768 + "inviteMember": "Пригласить участника" 1769 + }, 1770 + "inviteModal": { 1771 + "title": "Пригласить участника команды", 1772 + "emailLabel": "Электронная почта", 1773 + "emailPlaceholder": "colleague@company.com", 1774 + "sendInvitation": "Отправить приглашение", 1775 + "success": "Приглашение успешно отправлено", 1776 + "error": "Не удалось пригласить участника команды" 1777 + }, 1778 + "membersTable": { 1779 + "emptyTitle": "Участников команды пока нет", 1780 + "emptyDescription": "Пригласите первого участника команды, чтобы начать.", 1781 + "columns": { 1782 + "name": "Имя", 1783 + "role": "Роль", 1784 + "joined": "Присоединился", 1785 + "actions": "Действия" 1786 + }, 1787 + "memberRolePending": "{{role}} (Ожидает)", 1788 + "ariaCancelInvitation": "Отменить приглашение", 1789 + "ariaRemoveMember": "Удалить участника", 1790 + "removeDialogTitle": "Удалить участника команды?", 1791 + "removeDialogDescription": "Вы уверены, что хотите удалить {{name}} из рабочей области? Это действие нельзя отменить.", 1792 + "cancelDialogTitle": "Отменить приглашение?", 1793 + "cancelDialogDescription": "Вы уверены, что хотите отменить приглашение для {{email}}? Это действие нельзя отменить.", 1794 + "removeMember": "Удалить участника", 1795 + "cancelInvitation": "Отменить приглашение", 1796 + "removeSuccess": "Участник команды успешно удалён", 1797 + "removeError": "Не удалось удалить участника команды", 1798 + "cancelInviteSuccess": "Приглашение успешно отменено", 1799 + "cancelInviteError": "Не удалось отменить приглашение" 1800 + } 1801 + }, 1802 + "publicProject": { 1803 + "pageTitle": "Публичный просмотр", 1804 + "badge": "Публичный", 1805 + "readOnly": "Только для чтения", 1806 + "error": { 1807 + "title": "Проект не найден", 1808 + "description": "Этот проект не существует или недоступен публично." 1809 + }, 1810 + "taskCard": { 1811 + "viewDetailsAria": "Просмотреть детали задачи {{title}}" 1812 + }, 1813 + "taskDetail": { 1814 + "labels": "Метки", 1815 + "externalLinks": "Внешние ссылки", 1816 + "pullRequestFallback": "Pull Request", 1817 + "issueFallback": "Issue", 1818 + "prStatusMerged": "Влит", 1819 + "prStatusDraft": "Черновик", 1820 + "prStatusOpen": "Открыт", 1821 + "dueWithDate": "Срок: {{date}}", 1822 + "created": "Создано", 1823 + "dueDateLabel": "Срок выполнения" 1824 + }, 1825 + "theme": { 1826 + "switchToLight": "Переключить на светлую тему", 1827 + "switchToDark": "Переключить на тёмную тему" 1828 + }, 1829 + "copyUrl": { 1830 + "successToast": "URL скопирован", 1831 + "errorToast": "Не удалось скопировать URL", 1832 + "copied": "Скопировано", 1833 + "share": "Поделиться" 1834 + }, 1835 + "branding": { 1836 + "poweredBy": "Работает на" 1837 + } 1838 + } 1839 + }
+1797
i18n/uk-UA.json
··· 1 + { 2 + "common": { 3 + "appName": "Kaneo", 4 + "actions": { 5 + "cancel": "Скасувати", 6 + "close": "Закрити", 7 + "clearAll": "Очистити все", 8 + "delete": "Видалити", 9 + "deleting": "Видалення...", 10 + "markAllRead": "Позначити все як прочитане", 11 + "remove": "Прибрати", 12 + "reset": "Скинути", 13 + "filter": "Фільтр", 14 + "clearAllFilters": "Очистити всі фільтри" 15 + }, 16 + "a11y": { 17 + "toggleSidebar": "Перемикач бічної панелі" 18 + }, 19 + "sidebar": { 20 + "title": "Бічна панель", 21 + "mobileDescription": "Відображає мобільну бічну панель." 22 + }, 23 + "empty": { 24 + "loading": "Завантаження..." 25 + }, 26 + "pagination": { 27 + "label": "Пагінація", 28 + "previous": "Попередня", 29 + "next": "Наступна", 30 + "previousPage": "Перейти до попередньої сторінки", 31 + "nextPage": "Перейти до наступної сторінки", 32 + "morePages": "Більше сторінок" 33 + }, 34 + "breadcrumb": { 35 + "label": "Навігаційна стежка", 36 + "more": "Більше" 37 + }, 38 + "language": { 39 + "english": "Англійська", 40 + "german": "Німецька", 41 + "greek": "Грецька", 42 + "macedonian": "Македонська", 43 + "french": "Французька", 44 + "spanish": "Іспанська", 45 + "dutch": "Нідерландська" 46 + }, 47 + "people": { 48 + "someone": "Хтось", 49 + "unknown": "Невідомо" 50 + }, 51 + "error": { 52 + "title": "Щось пішло не так", 53 + "troubleshooting": "Кроки для усунення проблеми:", 54 + "tryAgain": "Спробувати ще раз", 55 + "viewDeploymentGuide": "Переглянути інструкцію з розгортання", 56 + "refreshPage": "Оновити сторінку" 57 + }, 58 + "formats": { 59 + "never": "Ніколи" 60 + }, 61 + "modals": { 62 + "createProject": { 63 + "title": "Створити новий проєкт", 64 + "breadcrumbNew": "Створити новий проєкт", 65 + "workspaceFallback": "РОБОЧИЙ ПРОСТІР", 66 + "description": "Створіть новий проєкт у вашому робочому просторі, вказавши назву, ключ та обравши іконку.", 67 + "pickIcon": "Обрати іконку", 68 + "searchIcons": "Пошук іконок...", 69 + "projectName": "Назва проєкту", 70 + "keyLabel": "Ключ:", 71 + "keyHint": "Використовується для ID тікетів (напр., {{example}}-123)", 72 + "createButton": "Створити проєкт", 73 + "successToast": "Проєкт успішно створено", 74 + "errorToast": "Не вдалося створити проєкт" 75 + }, 76 + "createWorkspace": { 77 + "breadcrumbKaneo": "KANEO", 78 + "title": "Створити новий робочий простір", 79 + "description": "Створіть новий робочий простір, вказавши його назву.", 80 + "namePlaceholder": "Назва робочого простору", 81 + "descriptionPlaceholder": "Додати опис...", 82 + "createButton": "Створити робочий простір", 83 + "successToast": "Робочий простір успішно створено", 84 + "errorToast": "Не вдалося створити робочий простір" 85 + }, 86 + "createTask": { 87 + "breadcrumbTask": "ЗАВДАННЯ", 88 + "title": "Нове завдання", 89 + "description": "Створіть нове завдання, вказавши назву, опис та інші деталі.", 90 + "taskTitlePlaceholder": "Назва завдання", 91 + "descriptionPlaceholder": "Додайте опис до вашого завдання...", 92 + "chooseProjectForImages": "Оберіть проєкт перед завантаженням зображень.", 93 + "prepareTaskError": "Не вдалося підготувати завдання", 94 + "successCreated": "Завдання успішно створено", 95 + "successUpdated": "Завдання успішно оновлено", 96 + "createError": "Не вдалося створити завдання", 97 + "priority": "Пріоритет", 98 + "statusFallback": "В роботі", 99 + "startDate": "Дата початку", 100 + "dueDate": "Дата завершення", 101 + "clearStartDate": "Очистити дату початку", 102 + "clearDueDate": "Очистити дату завершення", 103 + "assign": "Призначити", 104 + "assignUnassigned": "Не призначено", 105 + "assignUnassignedTitle": "Не призначено", 106 + "labels": "Мітки", 107 + "searchLabels": "Пошук міток...", 108 + "noLabelsFound": "Мітки не знайдено", 109 + "createLabel": "Створити «{{name}}»", 110 + "chooseColor": "Оберіть колір", 111 + "labelCreated": "Мітку створено", 112 + "labelCreateError": "Не вдалося створити мітку", 113 + "createMore": "Створити ще", 114 + "createButton": "Створити завдання", 115 + "untitledTask": "Завдання без назви", 116 + "labelColors": { 117 + "stone": "Камінь", 118 + "slate": "Сланець", 119 + "lavender": "Лаванда", 120 + "sage": "Шавлія", 121 + "forest": "Ліс", 122 + "amber": "Бурштин", 123 + "terracotta": "Теракота", 124 + "rose": "Троянда", 125 + "crimson": "Багряний" 126 + } 127 + } 128 + } 129 + }, 130 + "auth": { 131 + "signIn": { 132 + "pageTitle": "Вхід", 133 + "title": "З поверненням", 134 + "subtitle": "Введіть ваші облікові дані для доступу до робочого простору", 135 + "invitationSubtitle": "Увійдіть, щоб прийняти запрошення", 136 + "invitationAlert": "Після входу ви зможете прийняти запрошення до робочого простору.", 137 + "signingIn": "Вхід...", 138 + "continueWithGoogle": "Продовжити з Google", 139 + "continueWithGithub": "Продовжити з GitHub", 140 + "continueWithDiscord": "Продовжити з Discord", 141 + "continueWithOidc": "Продовжити з OIDC", 142 + "lastUsed": "Останній використаний", 143 + "registrationDisabled": "Публічну реєстрацію вимкнено. Використовуйте запрошення для створення облікового запису.", 144 + "passwordRegistrationDisabled": "Реєстрацію за допомогою пароля вимкнено. Використовуйте налаштований метод входу через соціальну мережу або OIDC.", 145 + "toggleMessage": "Не маєте облікового запису?", 146 + "toggleLink": "Створити обліковий запис", 147 + "guestSuccess": "Вхід як гість виконано", 148 + "guestError": "Не вдалося увійти як гість", 149 + "oidcError": "Не вдалося увійти через OIDC", 150 + "googleError": "Не вдалося увійти через Google", 151 + "githubError": "Не вдалося увійти через GitHub", 152 + "discordError": "Не вдалося увійти через Discord" 153 + }, 154 + "providers": { 155 + "google": "Google", 156 + "discord": "Discord" 157 + }, 158 + "forms": { 159 + "or": "або", 160 + "email": "Електронна пошта", 161 + "password": "Пароль", 162 + "emailPlaceholder": "me@example.com", 163 + "passwordPlaceholder": "••••••••", 164 + "showPassword": "Показати пароль", 165 + "hidePassword": "Сховати пароль" 166 + }, 167 + "checkEmail": { 168 + "pageTitle": "Перевірте пошту", 169 + "title": "Перевірте вашу пошту", 170 + "inboxMessage": "Ми надіслали вам тимчасове посилання для входу. Перевірте вхідні за адресою <email>{{email}}</email>.", 171 + "emailFallback": "вашу адресу електронної пошти", 172 + "backToLogin": "Повернутися до входу" 173 + }, 174 + "signUp": { 175 + "pageTitle": "Створення облікового запису", 176 + "title": "Створити обліковий запис", 177 + "subtitleInvitation": "Створіть обліковий запис, щоб прийняти запрошення", 178 + "subtitleRegistrationDisabled": "Для реєстрації потрібне запрошення", 179 + "subtitlePasswordDisabled": "Використовуйте вхід через соціальну мережу або OIDC для створення облікового запису", 180 + "subtitleDefault": "Почніть роботу з вашим робочим простором", 181 + "invitationAlert": "Після створення облікового запису ви зможете прийняти запрошення до робочого простору.", 182 + "registrationDisabledAlert": "Реєстрацію наразі вимкнено. Якщо вас запросили, введіть адресу електронної пошти, на яку було надіслано запрошення.", 183 + "passwordDisabledAlert": "Створення облікового запису за допомогою пароля вимкнено. Використовуйте налаштований метод входу через соціальну мережу або OIDC на сторінці входу.", 184 + "signingIn": "Вхід...", 185 + "continueAsGuest": "Продовжити як гість", 186 + "toggleMessage": "Вже маєте обліковий запис?", 187 + "toggleLink": "Увійти" 188 + }, 189 + "verifyOtp": { 190 + "pageTitle": "Підтвердження коду", 191 + "title": "Введіть код підтвердження", 192 + "subtitle": "Використовуйте 6-значний код, надісланий на вашу пошту, щоб продовжити", 193 + "codeSentTo": "Код надіслано на {{email}}", 194 + "verificationCodeLabel": "Код підтвердження", 195 + "verifying": "Перевірка...", 196 + "verifyAndSignIn": "Підтвердити та увійти", 197 + "changeEmail": "Змінити пошту", 198 + "resend": "Надіслати повторно", 199 + "validation": { 200 + "codeLength": "Код має містити 6 цифр" 201 + }, 202 + "toast": { 203 + "invalidCode": "Невірний код підтвердження", 204 + "signedInSuccess": "Вхід виконано успішно!", 205 + "verifyFailed": "Не вдалося підтвердити код", 206 + "resendFailed": "Не вдалося надіслати код повторно", 207 + "resendSuccess": "Новий код підтвердження надіслано!" 208 + } 209 + }, 210 + "otpSignIn": { 211 + "sendFailed": "Не вдалося надіслати код підтвердження", 212 + "codeSent": "Код підтвердження надіслано! Перевірте вашу пошту.", 213 + "sending": "Надсилання...", 214 + "sendVerificationCode": "Надіслати код підтвердження" 215 + }, 216 + "signInForm": { 217 + "failedSignIn": "Не вдалося увійти", 218 + "signedInSuccess": "Вхід виконано успішно", 219 + "signingIn": "Вхід...", 220 + "signIn": "Увійти" 221 + }, 222 + "signUpForm": { 223 + "fullName": "Повне ім'я", 224 + "namePlaceholder": "Іван Петренко", 225 + "failedSignUp": "Не вдалося зареєструватися", 226 + "accountCreated": "Обліковий запис успішно створено", 227 + "passwordTooShort": "Пароль занадто короткий", 228 + "creatingAccount": "Створення облікового запису...", 229 + "createAccount": "Створити обліковий запис" 230 + }, 231 + "invitation": { 232 + "pageTitleAccept": "Прийняти запрошення", 233 + "pageTitleError": "Помилка запрошення", 234 + "pageTitleInvalid": "Недійсне запрошення", 235 + "loadingTitle": "Завантаження запрошення...", 236 + "errorTitle": "Помилка запрошення", 237 + "invalidTitle": "Недійсне запрошення", 238 + "invitationExpired": "Запрошення прострочено", 239 + "errorLoadDescription": "Не вдалося завантажити деталі запрошення. Запрошення може бути недійсним або простроченим.", 240 + "goToSignIn": "Перейти до входу", 241 + "workspaceLabel": "Робочий простір: {{workspaceName}}", 242 + "joinWorkspace": "Приєднатися до {{workspaceName}}", 243 + "inviteBodySignedIn": "<inviter>{{inviterName}}</inviter> запросив вас приєднатися до свого робочого простору.", 244 + "inviteBodySignedOut": "<inviter>{{inviterName}}</inviter> запросив вас приєднатися до свого робочого простору в Kaneo.", 245 + "signInToAccept": "Увійдіть, щоб прийняти це запрошення.", 246 + "accepting": "Приймається...", 247 + "acceptInvitation": "Прийняти запрошення", 248 + "goToDashboard": "Перейти до панелі керування", 249 + "signedInAs": "Ви увійшли як <email>{{email}}</email>", 250 + "youveBeenInvited": "Вас запрошено!", 251 + "invitationFor": "Запрошення для: <email>{{email}}</email>", 252 + "signIn": "Увійти", 253 + "toast": { 254 + "acceptFailed": "Не вдалося прийняти запрошення", 255 + "acceptSuccess": "Запрошення прийнято! Ласкаво просимо до команди." 256 + } 257 + }, 258 + "onboarding": { 259 + "pageTitle": "Ласкаво просимо до Kaneo", 260 + "workspacePageTitle": "Створення робочого простору", 261 + "createWorkspaceTitle": "Створити робочий простір", 262 + "createWorkspaceSubtitle": "Налаштуйте робочий простір, щоб почати керувати проєктами", 263 + "workspaceName": "Назва робочого простору", 264 + "workspaceNamePlaceholder": "напр. Acme Inc, Моя команда", 265 + "descriptionOptional": "Опис (необов'язково)", 266 + "descriptionPlaceholder": "Над чим працює ваша команда?", 267 + "creating": "Створення...", 268 + "createWorkspace": "Створити робочий простір", 269 + "workspaceCreatedTitle": "Робочий простір створено", 270 + "redirectingToWorkspace": "Переходимо до <name>{{name}}</name>...", 271 + "toast": { 272 + "workspaceCreated": "Робочий простір успішно створено", 273 + "createFailed": "Не вдалося створити робочий простір" 274 + }, 275 + "validation": { 276 + "workspaceNameRequired": "Назва робочого простору є обов'язковою" 277 + } 278 + }, 279 + "profileSetup": { 280 + "pageTitle": "Заповнення профілю", 281 + "completeTitle": "Заповніть ваш профіль", 282 + "subtitle": "Будь ласка, введіть ваше ім'я, щоб почати", 283 + "yourName": "Ваше ім'я", 284 + "namePlaceholder": "напр. Іван Петренко", 285 + "saving": "Збереження...", 286 + "continue": "Продовжити", 287 + "welcome": "Ласкаво просимо, {{name}}!", 288 + "redirecting": "Переходимо до вашої панелі керування...", 289 + "toast": { 290 + "updateSuccess": "Профіль успішно оновлено", 291 + "updateFailed": "Не вдалося оновити профіль" 292 + }, 293 + "validation": { 294 + "nameRequired": "Ім'я є обов'язковим", 295 + "nameShort": "Ім'я має містити щонайменше 2 символи" 296 + } 297 + } 298 + }, 299 + "settings": { 300 + "account": "Обліковий запис", 301 + "developer": "Розробник", 302 + "information": "Інформація", 303 + "notifications": "Сповіщення", 304 + "preferences": "Налаштування", 305 + "apiKeys": "API-ключі", 306 + "informationPage": { 307 + "pageTitle": "Особиста інформація", 308 + "title": "Особиста інформація", 309 + "subtitle": "Керуйте вашими особистими даними та інформацією облікового запису.", 310 + "sectionTitle": "Інформація облікового запису", 311 + "sectionSubtitle": "Керуйте вашим профілем та деталями облікового запису.", 312 + "profilePicture": "Фото профілю", 313 + "fullName": "Повне ім'я", 314 + "fullNamePlaceholder": "Введіть ваше ім'я", 315 + "email": "Електронна пошта", 316 + "emailPlaceholder": "Введіть вашу пошту", 317 + "updateSuccess": "Профіль успішно оновлено", 318 + "updateError": "Не вдалося оновити профіль", 319 + "validation": { 320 + "nameRequired": "Ім'я є обов'язковим", 321 + "nameShort": "Ім'я має містити щонайменше 2 символи", 322 + "invalidEmail": "Невірна адреса електронної пошти" 323 + } 324 + }, 325 + "notificationsPage": { 326 + "pageTitle": "Сповіщення", 327 + "title": "Сповіщення", 328 + "subtitle": "Оберіть, як Kaneo надсилатиме сповіщення вашого облікового запису та які канали використовувати.", 329 + "statusConnected": "Підключено", 330 + "statusPaused": "Призупинено", 331 + "emailTitle": "Електронна пошта", 332 + "emailDescription": "Використовуйте пошту вашого облікового запису як адресу для сповіщень.", 333 + "accountEmailLabel": "Пошта облікового запису", 334 + "accountEmailNoAddress": "Пошта облікового запису відсутня", 335 + "accountEmailHint": "Листи завжди надсилаються на пошту поточного облікового запису.", 336 + "saveChanges": "Зберегти зміни", 337 + "disconnect": "Від'єднати", 338 + "ntfyTitle": "ntfy", 339 + "ntfyDescription": "Публікуйте сповіщення облікового запису в тему ntfy.", 340 + "serverUrl": "URL сервера", 341 + "topic": "Тема", 342 + "token": "Токен", 343 + "ntfyServerPlaceholder": "https://ntfy.example.com", 344 + "ntfyTopicPlaceholder": "team-alerts", 345 + "ntfyTokenPlaceholder": "Необов'язковий bearer-токен", 346 + "ntfyTokenHintConfigured": "Токен вже налаштовано ({{masked}}). Введіть новий токен, щоб замінити його.", 347 + "ntfyTokenHintOptional": "Необов'язково. Вкажіть токен, якщо ваш сервер ntfy вимагає автентифікації.", 348 + "connectNtfy": "Підключити ntfy", 349 + "gotifyTitle": "Gotify", 350 + "gotifyDescription": "Надсилайте сповіщення облікового запису на ваш сервер Gotify.", 351 + "gotifyTokenLabel": "Токен застосунку", 352 + "gotifyServerPlaceholder": "https://gotify.example.com", 353 + "gotifyTokenPlaceholder": "Токен застосунку Gotify", 354 + "gotifyTokenHintConfigured": "Токен застосунку вже налаштовано ({{masked}}). Введіть новий токен, щоб замінити його.", 355 + "gotifyTokenHintRequired": "Обов'язково. Використовуйте токен застосунку з вашого сервера Gotify.", 356 + "connectGotify": "Підключити Gotify", 357 + "webhookTitle": "Власний вебхук", 358 + "webhookDescription": "Надсилайте сповіщення облікового запису на вашу кінцеву точку у форматі JSON.", 359 + "endpointUrl": "URL кінцевої точки", 360 + "signingSecret": "Секрет підпису", 361 + "webhookUrlPlaceholder": "https://example.com/webhooks/kaneo", 362 + "webhookSecretPlaceholder": "Необов'язковий спільний секрет", 363 + "webhookSecretHintConfigured": "Секрет підпису вже налаштовано ({{masked}}). Введіть новий, щоб замінити його.", 364 + "webhookSecretHintOptional": "Необов'язково. Kaneo підписує тіло запиту, коли секрет встановлено.", 365 + "connectWebhook": "Підключити вебхук", 366 + "workspaceRulesTitle": "Правила доставки по робочих просторах", 367 + "workspaceRulesDescription": "Використовуйте ваші глобальні канали, а потім визначте, які робочі простори та проєкти можуть надсилати сповіщення облікового запису.", 368 + "workspaceCardHint": "Оберіть, які канали цей робочий простір може використовувати для сповіщень облікового запису.", 369 + "workspaceCardLabelEmail": "Пошта", 370 + "workspaceCardLabelNtfy": "ntfy", 371 + "workspaceCardLabelGotify": "Gotify", 372 + "workspaceCardLabelWebhook": "Власний вебхук", 373 + "emailChannelHintEnabled": "Надсилати відповідні сповіщення робочого простору електронною поштою.", 374 + "emailChannelHintDisabled": "Спочатку налаштуйте та увімкніть пошту глобально.", 375 + "ntfyChannelHintEnabled": "Надсилати відповідні сповіщення робочого простору в ntfy.", 376 + "ntfyChannelHintDisabled": "Спочатку налаштуйте та увімкніть ntfy глобально.", 377 + "gotifyChannelHintEnabled": "Надсилати відповідні сповіщення робочого простору в Gotify.", 378 + "gotifyChannelHintDisabled": "Спочатку налаштуйте та увімкніть Gotify глобально.", 379 + "webhookChannelHintEnabled": "Надсилати відповідні сповіщення робочого простору на ваш вебхук.", 380 + "webhookChannelHintDisabled": "Спочатку налаштуйте та увімкніть вебхук глобально.", 381 + "projectScope": "Область проєктів", 382 + "projectScopeDescription": "Усі проєкти включені за замовчуванням. Звузьте вибір, якщо цей робочий простір має надсилати сповіщення лише з обраних проєктів.", 383 + "allProjects": "Усі проєкти", 384 + "allProjectsDescription": "Доставляти сповіщення з кожного проєкту в цьому робочому просторі.", 385 + "selectedProjects": "Обрані проєкти", 386 + "selectedProjectsDescription": "Доставляти сповіщення лише з обраних проєктів.", 387 + "noProjectsInWorkspace": "У цьому робочому просторі ще немає доступних проєктів.", 388 + "createRule": "Створити правило", 389 + "removeRule": "Видалити правило", 390 + "toastEmailSaved": "Налаштування сповіщень електронною поштою збережено", 391 + "toastEmailSaveFailed": "Не вдалося зберегти налаштування пошти", 392 + "toastNtfySaved": "Налаштування ntfy збережено", 393 + "toastNtfySaveFailed": "Не вдалося зберегти налаштування ntfy", 394 + "toastNtfyDisconnected": "ntfy від'єднано", 395 + "toastNtfyDisconnectFailed": "Не вдалося від'єднати ntfy", 396 + "toastGotifySaved": "Налаштування Gotify збережено", 397 + "toastGotifySaveFailed": "Не вдалося зберегти налаштування Gotify", 398 + "toastGotifyDisconnected": "Gotify від'єднано", 399 + "toastGotifyDisconnectFailed": "Не вдалося від'єднати Gotify", 400 + "toastWebhookSaved": "Налаштування вебхука збережено", 401 + "toastWebhookSaveFailed": "Не вдалося зберегти налаштування вебхука", 402 + "toastWebhookDisconnected": "Вебхук від'єднано", 403 + "toastWebhookDisconnectFailed": "Не вдалося від'єднати вебхук", 404 + "toastRuleSaved": "Правило сповіщень для {{workspaceName}} збережено", 405 + "toastRuleSaveFailed": "Не вдалося зберегти правило сповіщень робочого простору", 406 + "toastRuleRemoved": "Правило сповіщень для {{workspaceName}} видалено", 407 + "toastRuleRemoveFailed": "Не вдалося видалити правило сповіщень робочого простору", 408 + "toastPreferencesSaved": "Налаштування сповіщень збережено", 409 + "toastPreferencesSaveFailed": "Не вдалося зберегти налаштування сповіщень", 410 + "toastRuleSavedGeneric": "Правило сповіщень робочого простору збережено", 411 + "toastRuleRemovedGeneric": "Правило сповіщень робочого простору видалено" 412 + }, 413 + "preferencesPage": { 414 + "title": "Налаштування", 415 + "subtitle": "Налаштуйте Kaneo під себе.", 416 + "appearanceTitle": "Вигляд", 417 + "appearanceSubtitle": "Візуальні налаштування та налаштування макета.", 418 + "theme": "Тема", 419 + "themeDescription": "Оберіть бажану колірну схему", 420 + "selectTheme": "Обрати тему", 421 + "themeLight": "Світла", 422 + "themeDark": "Темна", 423 + "themeSystem": "Системна", 424 + "language": "Мова", 425 + "languageDescription": "Оберіть бажану мову інтерфейсу", 426 + "selectLanguage": "Обрати мову", 427 + "defaultView": "Перегляд за замовчуванням", 428 + "defaultViewDescription": "Оберіть бажаний режим перегляду завдань", 429 + "selectViewMode": "Обрати режим перегляду", 430 + "board": "Дошка", 431 + "list": "Список", 432 + "gantt": "Ґант", 433 + "sidebarDefault": "Бічна панель за замовчуванням", 434 + "sidebarDefaultDescription": "Тримати бічну панель розгорнутою при запуску", 435 + "displayOptions": "Параметри відображення", 436 + "displayOptionsDescription": "Оберіть, яку інформацію показувати в представленнях завдань", 437 + "taskNumbers": "Номери завдань", 438 + "taskNumbersDescription": "Показувати ID та номери завдань", 439 + "assignees": "Виконавці", 440 + "assigneesDescription": "Показувати, хто призначений на завдання", 441 + "dueDates": "Дати завершення", 442 + "dueDatesDescription": "Відображати крайні строки завдань", 443 + "labels": "Мітки", 444 + "labelsDescription": "Показувати мітки та теґи завдань", 445 + "priority": "Пріоритет", 446 + "priorityDescription": "Відображати індикатори пріоритету" 447 + }, 448 + "developerPage": { 449 + "pageTitle": "Налаштування розробника", 450 + "title": "Налаштування розробника", 451 + "subtitle": "Керуйте вашими API-ключами та ресурсами для розробників.", 452 + "apiKeysCardTitle": "API-ключі", 453 + "apiKeysCardDescription": "Створюйте та керуйте API-ключами для програмного доступу до Kaneo.", 454 + "createApiKey": "Створити API-ключ", 455 + "unnamedKey": "Ключ без назви" 456 + }, 457 + "apiKey": { 458 + "createdModal": { 459 + "title": "API-ключ створено", 460 + "description": "Ваш API-ключ «{{keyName}}» успішно створено.", 461 + "yourApiKey": "Ваш API-ключ", 462 + "copy": "Копіювати", 463 + "copied": "Скопійовано", 464 + "toastCopied": "API-ключ скопійовано до буфера обміну", 465 + "alertTitle": "Ваш API-ключ успішно створено", 466 + "alertDescription": "Скопіюйте цей ключ зараз. Ви більше не зможете його побачити.", 467 + "done": "Готово", 468 + "copyToContinue": "Скопіюйте ключ, щоб продовжити" 469 + }, 470 + "table": { 471 + "loading": "Завантаження API-ключів...", 472 + "empty": "API-ключів ще немає. Створіть один, щоб почати.", 473 + "columnName": "Назва", 474 + "columnKey": "Ключ", 475 + "columnCreated": "Створено", 476 + "columnExpires": "Закінчується", 477 + "columnActions": "Дії", 478 + "unnamedKey": "Ключ без назви", 479 + "expiredBadge": "Закінчився {{label}}", 480 + "deleteConfirmTitle": "Видалити API-ключ?", 481 + "deleteConfirmDescription": "Цю дію неможливо скасувати. API-ключ {{name}} буде видалено назавжди.", 482 + "deleteFallbackName": "цей API-ключ", 483 + "delete": "Видалити", 484 + "deleting": "Видалення...", 485 + "deleteAria": "Видалити {{name}}", 486 + "deleteAriaFallback": "API-ключ", 487 + "toastDeleted": "API-ключ успішно видалено", 488 + "toastDeleteError": "Не вдалося видалити API-ключ" 489 + }, 490 + "createDialog": { 491 + "title": "Створити API-ключ", 492 + "description": "Створіть новий API-ключ для програмного доступу до Kaneo API.", 493 + "nameLabel": "Назва", 494 + "namePlaceholder": "Мій API-ключ", 495 + "nameDescription": "Описова назва для цього API-ключа", 496 + "expirationLabel": "Термін дії", 497 + "expirationPlaceholder": "Оберіть термін дії", 498 + "expirationDescription": "Оберіть, як довго цей API-ключ буде дійсним. «Безстроково» створить ключ без автоматичного закінчення.", 499 + "expiration1d": "1 день", 500 + "expiration7d": "7 днів", 501 + "expiration30d": "30 днів", 502 + "expiration90d": "90 днів", 503 + "expirationNever": "Безстроково", 504 + "create": "Створити", 505 + "creating": "Створення...", 506 + "failedCreate": "Не вдалося створити API-ключ", 507 + "validation": { 508 + "nameRequired": "Назва є обов'язковою", 509 + "nameShort": "Назва має містити щонайменше 3 символи", 510 + "expirationRequired": "Термін дії є обов'язковим" 511 + } 512 + } 513 + }, 514 + "workspaceGeneral": { 515 + "pageTitle": "Загальні налаштування", 516 + "title": "Загальні налаштування", 517 + "subtitle": "Керуйте назвою та описом вашого робочого простору.", 518 + "workspaceInfoTitle": "Інформація про робочий простір", 519 + "workspaceInfoSubtitle": "Налаштуйте деталі та параметри вашого робочого простору.", 520 + "nameLabel": "Назва робочого простору", 521 + "nameHint": "Назва вашого робочого простору", 522 + "namePlaceholder": "Введіть назву робочого простору", 523 + "descriptionLabel": "Опис", 524 + "descriptionHint": "Короткий опис вашого робочого простору", 525 + "descriptionPlaceholder": "Введіть опис робочого простору", 526 + "dangerZone": "Небезпечна зона", 527 + "dangerZoneSubtitle": "Незворотні та руйнівні дії.", 528 + "deleteWorkspace": "Видалити робочий простір", 529 + "deleteWorkspaceDescription": "Запланувати остаточне видалення робочого простору", 530 + "deleteModalTitle": "Видалити робочий простір?", 531 + "deleteModalDescription": "Це назавжди видалить робочий простір «{{name}}» та всі його дані. Цю дію неможливо скасувати.", 532 + "deleteModalConfirm": "Видалити робочий простір", 533 + "toastUpdated": "Робочий простір успішно оновлено", 534 + "toastUpdateError": "Не вдалося оновити робочий простір", 535 + "toastDeleted": "Робочий простір успішно видалено", 536 + "toastDeleteError": "Не вдалося видалити робочий простір", 537 + "validation": { 538 + "nameRequired": "Назва робочого простору є обов'язковою", 539 + "nameShort": "Назва робочого простору має містити щонайменше 2 символи" 540 + } 541 + }, 542 + "projectGeneral": { 543 + "pageTitle": "Налаштування проєкту", 544 + "title": "Загальні налаштування", 545 + "subtitle": "Керуйте назвою, ключем, іконкою та описом вашого проєкту.", 546 + "projectInfoTitle": "Інформація про проєкт", 547 + "projectInfoSubtitle": "Налаштуйте деталі та параметри вашого проєкту.", 548 + "iconLabel": "Іконка", 549 + "iconHint": "Відображається в бічній панелі та на поверхнях проєкту.", 550 + "pickIconTitle": "Обрати іконку", 551 + "searchIconsPlaceholder": "Пошук іконок...", 552 + "projectNameLabel": "Назва проєкту", 553 + "projectNameHint": "Назва вашого проєкту", 554 + "projectNamePlaceholder": "Введіть назву проєкту", 555 + "keyLabel": "Ключ", 556 + "keyHint": "Використовується для ID тікетів (напр., {{slug}}-123)", 557 + "keyPlaceholder": "PRO", 558 + "descriptionLabel": "Опис", 559 + "descriptionHint": "Короткий опис вашого проєкту", 560 + "descriptionPlaceholder": "Введіть опис проєкту", 561 + "dangerZone": "Небезпечна зона", 562 + "dangerZoneSubtitle": "Незворотні та руйнівні дії.", 563 + "deleteProject": "Видалити проєкт", 564 + "deleteProjectDescription": "Запланувати остаточне видалення проєкту", 565 + "deleteModalTitle": "Видалити проєкт?", 566 + "deleteModalDescription": "Це назавжди видалить проєкт «{{name}}» та всі його дані. Цю дію неможливо скасувати.", 567 + "deleteModalConfirm": "Видалити проєкт", 568 + "toastUpdated": "Проєкт успішно оновлено", 569 + "toastUpdateError": "Не вдалося оновити проєкт", 570 + "toastDeleted": "Проєкт успішно видалено", 571 + "toastDeleteError": "Не вдалося видалити проєкт", 572 + "validation": { 573 + "nameRequired": "Назва проєкту є обов'язковою", 574 + "nameShort": "Назва проєкту має містити щонайменше 2 символи", 575 + "keyRequired": "Ключ є обов'язковим", 576 + "keyShort": "Ключ має містити щонайменше 2 символи", 577 + "keyMax": "Ключ може містити не більше 8 символів", 578 + "iconRequired": "Іконка є обов'язковою" 579 + } 580 + }, 581 + "projectIntegrations": { 582 + "pageTitle": "Інтеґрації проєкту", 583 + "title": "Інтеґрації проєкту", 584 + "subtitle": "З'єднайте ваш проєкт із зовнішніми інструментами та сервісами для оптимізації робочого процесу.", 585 + "githubSectionTitle": "Інтеґрація з GitHub", 586 + "githubSectionSubtitle": "Синхронізуйте завдання з GitHub Issues та увімкніть двосторонню синхронізацію.", 587 + "giteaSectionTitle": "Інтеґрація з Gitea", 588 + "giteaSectionSubtitle": "Синхронізуйте завдання з власним екземпляром Gitea (issues, PR, вебхуки).", 589 + "discordSectionTitle": "Інтеґрація з Discord", 590 + "discordSectionSubtitle": "Надсилайте оновлення завдань проєкту до каналу Discord за допомогою вебхука.", 591 + "genericWebhookSectionTitle": "Загальні вебхуки", 592 + "genericWebhookSectionSubtitle": "Надсилайте події завдань проєкту на будь-яку HTTP-кінцеву точку у форматі JSON.", 593 + "slackSectionTitle": "Інтеґрація зі Slack", 594 + "slackSectionSubtitle": "Надсилайте оновлення завдань проєкту до каналу Slack за допомогою вхідного вебхука.", 595 + "telegramSectionTitle": "Інтеґрація з Telegram", 596 + "telegramSectionSubtitle": "Надсилайте оновлення завдань проєкту до чату або теми Telegram за допомогою бота." 597 + }, 598 + "projectVisibility": { 599 + "pageTitle": "Видимість проєкту", 600 + "title": "Видимість", 601 + "subtitle": "Контролюйте, хто може переглядати ваш проєкт та мати до нього доступ.", 602 + "sectionTitle": "Видимість", 603 + "sectionSubtitle": "Перемикайте публічний доступ та поширюйте публічне посилання.", 604 + "publicAccess": "Публічний доступ", 605 + "publicAccessHint": "Дозволити перегляд цього проєкту всім, хто має посилання", 606 + "publicUrl": "Публічне посилання", 607 + "publicUrlHint": "Поділіться цим посиланням, якщо проєкт публічний", 608 + "copy": "Копіювати", 609 + "copiedToast": "Скопійовано", 610 + "toastUpdated": "Видимість оновлено", 611 + "toastUpdateError": "Не вдалося оновити видимість" 612 + }, 613 + "projectWorkflow": { 614 + "pageTitle": "Налаштування робочого процесу", 615 + "title": "Робочий процес", 616 + "subtitle": "Налаштуйте колонки дошки та правила автоматизації для цього проєкту.", 617 + "columnsTitle": "Колонки", 618 + "columnsDescription": "Керуйте колонками, які відображаються на вашій дошці. Перетягуйте для зміни порядку. Увімкніть «Колонка готовності» для етапів, які означають завершену роботу.", 619 + "automationTitle": "Правила автоматизації", 620 + "automationDescription": "Зіставте події інтеґрацій із колонками. Коли подія відбувається, пов'язане завдання переміщується до вказаної колонки." 621 + }, 622 + "projectSwitcher": { 623 + "selectProject": "Обрати проєкт", 624 + "noProjects": "Немає проєктів" 625 + }, 626 + "columnEditor": { 627 + "toastCreated": "Колонку створено", 628 + "toastCreateError": "Не вдалося створити колонку", 629 + "toastRenamed": "Колонку перейменовано", 630 + "toastRenameError": "Не вдалося оновити колонку", 631 + "toastFinalOn": "Колонку позначено як фінальну", 632 + "toastFinalOff": "Позначення колонки як фінальної знято", 633 + "toastUpdateError": "Не вдалося оновити колонку", 634 + "toastDeleted": "Колонку видалено", 635 + "toastDeleteError": "Не вдалося видалити колонку", 636 + "loading": "Завантаження колонок...", 637 + "doneColumnTooltip": "Вважати цю колонку колонкою готовності", 638 + "doneColumn": "Колонка готовності", 639 + "markDoneAria": "Позначити {{name}} як колонку готовності", 640 + "on": "Увімк.", 641 + "off": "Вимк.", 642 + "newColumnPlaceholder": "Назва нової колонки...", 643 + "add": "Додати" 644 + }, 645 + "githubIntegration": { 646 + "validation": { 647 + "ownerRequired": "Власник репозиторію є обов'язковим", 648 + "ownerInvalid": "Невірний формат власника репозиторію", 649 + "nameRequired": "Назва репозиторію є обов'язковою", 650 + "nameInvalid": "Невірний формат назви репозиторію" 651 + }, 652 + "toast": { 653 + "installedOk": "GitHub App встановлено коректно!", 654 + "installedMissingPerms": "GitHub App встановлено, але відсутні необхідні дозволи", 655 + "needsInstallOnRepo": "GitHub App потрібно встановити на цей репозиторій", 656 + "repoNotFound": "Репозиторій не знайдено або немає доступу", 657 + "verifyError": "Не вдалося перевірити встановлення GitHub", 658 + "installAppFirst": "Спочатку встановіть GitHub App на цей репозиторій", 659 + "missingPermsDetail": "GitHub App не має необхідних дозволів: {{list}}. Оновіть дозволи застосунку.", 660 + "updated": "Інтеґрацію з GitHub успішно оновлено", 661 + "updateError": "Не вдалося оновити інтеґрацію з GitHub", 662 + "removed": "Інтеґрацію з GitHub успішно видалено", 663 + "removeError": "Не вдалося видалити інтеґрацію з GitHub", 664 + "issuesImported": "Issues успішно імпортовано", 665 + "importError": "Не вдалося імпортувати issues", 666 + "commentOnEnabled": "Kaneo коментуватиме посилання на завдання в нових issues", 667 + "commentOnDisabled": "Коментарі з посиланнями на завдання в нових issues вимкнено", 668 + "settingsUpdateError": "Не вдалося оновити інтеґрацію з GitHub" 669 + }, 670 + "connectionStatus": "Статус з'єднання", 671 + "connectedActive": "Репозиторій підключено та активно", 672 + "notConnectedHint": "Репозиторій не підключено", 673 + "badgeConnected": "Підключено", 674 + "badgeNotConnected": "Не підключено", 675 + "repository": "Репозиторій", 676 + "repositoryHint": "Підключений репозиторій GitHub", 677 + "commentTaskLinkTitle": "Коментувати посилання Kaneo в нових issues", 678 + "commentTaskLinkHint": "Коли увімкнено, Kaneo публікує коментар у кожному новому GitHub issue з посиланням на завдання.", 679 + "appStatusTitle": "Статус GitHub App", 680 + "appStatusHint": "Статус встановлення та дозволів", 681 + "statusProperlyConfigured": "Налаштовано правильно", 682 + "statusMissingPermissions": "Відсутні дозволи", 683 + "statusNotInstalled": "Не встановлено", 684 + "ownerLabel": "Власник репозиторію", 685 + "ownerHint": "Ім'я користувача або організації GitHub", 686 + "ownerPlaceholder": "напр., octocat", 687 + "repoNameLabel": "Назва репозиторію", 688 + "repoNameHint": "Назва репозиторію", 689 + "repoNamePlaceholder": "напр., my-project", 690 + "actionsTitle": "Дії", 691 + "actionsHint": "Керуйте підключенням до репозиторію", 692 + "browse": "Переглянути", 693 + "verify": "Перевірити", 694 + "update": "Оновити", 695 + "connect": "Підключити", 696 + "disconnect": "Від'єднати", 697 + "missingPermissionsLabel": "Відсутні дозволи:", 698 + "updatePermissions": "Оновити дозволи", 699 + "installGithubApp": "Встановити GitHub App", 700 + "importSectionTitle": "Імпорт GitHub Issues", 701 + "importSectionHint": "Імпортуйте наявні issues з вашого GitHub-репозиторію як завдання", 702 + "importing": "Імпорт...", 703 + "importIssues": "Імпортувати Issues", 704 + "importDisabledHint": "Завершіть налаштування репозиторію вище, щоб увімкнути імпорт" 705 + }, 706 + "giteaIntegration": { 707 + "validation": { 708 + "baseUrlRequired": "Базова URL-адреса Gitea є обов'язковою", 709 + "baseUrlInvalid": "Введіть дійсну URL-адресу (напр. https://gitea.example.com)", 710 + "ownerRequired": "Власник репозиторію є обов'язковим", 711 + "ownerInvalid": "Невірний формат власника репозиторію", 712 + "nameRequired": "Назва репозиторію є обов'язковою", 713 + "nameInvalid": "Невірний формат назви репозиторію" 714 + }, 715 + "toast": { 716 + "verifyOk": "Токен Gitea має доступ до цього репозиторію", 717 + "verifyWarning": "Перевірте дозволи токена або доступ до репозиторію", 718 + "repoNotFound": "Репозиторій не знайдено або немає доступу", 719 + "verifyError": "Не вдалося перевірити доступ до Gitea", 720 + "tokenRequired": "Персональний токен доступу є обов'язковим", 721 + "tokenRequiredVerify": "Введіть токен для перевірки", 722 + "verifyFirst": "Перевірка доступу не вдалася — перевірте URL, токен та репозиторій", 723 + "updated": "Інтеґрацію з Gitea збережено", 724 + "updateError": "Не вдалося зберегти інтеґрацію з Gitea", 725 + "removed": "Інтеґрацію з Gitea видалено", 726 + "removeError": "Не вдалося видалити інтеґрацію з Gitea", 727 + "issuesImported": "Issues успішно імпортовано", 728 + "importError": "Не вдалося імпортувати issues", 729 + "commentOnEnabled": "Kaneo коментуватиме посилання на завдання в нових issues", 730 + "commentOnDisabled": "Коментарі з посиланнями на завдання в нових issues вимкнено", 731 + "settingsUpdateError": "Не вдалося оновити інтеґрацію з Gitea", 732 + "secretCopied": "Скопійовано", 733 + "unableToCopySecret": "Не вдалося скопіювати секрет" 734 + }, 735 + "webhookShow": "Показати", 736 + "webhookHide": "Сховати", 737 + "webhookCopy": "Копіювати", 738 + "connectionStatus": "Статус з'єднання", 739 + "connectedActive": "Репозиторій підключено та активно", 740 + "notConnectedHint": "Gitea-репозиторій не підключено", 741 + "badgeConnected": "Підключено", 742 + "badgeNotConnected": "Не підключено", 743 + "repository": "Репозиторій", 744 + "repositoryHint": "Пов'язаний Gitea-репозиторій", 745 + "commentTaskLinkTitle": "Коментувати посилання Kaneo в нових issues", 746 + "commentTaskLinkHint": "Коли увімкнено, Kaneo публікує коментар у кожному новому issue з посиланням на завдання.", 747 + "webhookTitle": "Вебхук", 748 + "webhookHint": "У вашому Gitea-репозиторії додайте вебхук із цією URL-адресою та секретом. Увімкніть push, pull request, issues, коментарі до issues та create (для міток).", 749 + "webhookSecretLabel": "Секрет (має збігатися з секретом вебхука в Gitea)", 750 + "baseUrlLabel": "URL Gitea", 751 + "baseUrlHint": "Коренева URL-адреса вашого екземпляра Gitea", 752 + "tokenLabel": "Персональний токен доступу", 753 + "tokenHint": "Токен із доступом до репозиторію та issues", 754 + "tokenPlaceholder": "Вставте токен", 755 + "tokenPlaceholderUpdate": "Вставте новий токен для заміни", 756 + "currentToken": "збережено", 757 + "ownerLabel": "Власник", 758 + "ownerHint": "Ім'я користувача або організації", 759 + "repoNameLabel": "Назва репозиторію", 760 + "repoNameHint": "Лише назва репозиторію (без власника)", 761 + "actionsTitle": "Дії", 762 + "actionsHint": "Перевірте доступ та підключіться", 763 + "browse": "Переглянути", 764 + "verify": "Перевірити", 765 + "update": "Оновити", 766 + "connect": "Підключити", 767 + "disconnect": "Від'єднати", 768 + "importSectionTitle": "Імпорт Gitea issues", 769 + "importSectionHint": "Імпортуйте відкриті issues та pull requests із репозиторію", 770 + "importing": "Імпорт...", 771 + "importIssues": "Імпортувати issues", 772 + "importDisabledHint": "Перевірте репозиторій вище, щоб увімкнути імпорт", 773 + "browseModalTitle": "Ваші репозиторії", 774 + "browseModalHint": "Репозиторії, до яких має доступ ваш токен", 775 + "searchRepos": "Пошук...", 776 + "browseNeedsCredentials": "Введіть URL Gitea та токен для перегляду", 777 + "loadingRepos": "Завантаження репозиторіїв...", 778 + "retry": "Повторити" 779 + }, 780 + "slackIntegration": { 781 + "validation": { 782 + "webhookInvalid": "Введіть дійсну URL-адресу вебхука Slack" 783 + }, 784 + "toast": { 785 + "saved": "Інтеґрацію зі Slack успішно збережено", 786 + "saveError": "Не вдалося зберегти інтеґрацію зі Slack", 787 + "enabled": "Сповіщення Slack увімкнено", 788 + "disabled": "Сповіщення Slack призупинено", 789 + "updateError": "Не вдалося оновити інтеґрацію зі Slack", 790 + "removed": "Інтеґрацію зі Slack успішно видалено", 791 + "removeError": "Не вдалося видалити інтеґрацію зі Slack" 792 + }, 793 + "connectionTitle": "Підключення вебхука Slack", 794 + "connectionHint": "Вставте URL вхідного вебхука Slack та оберіть, які події завдань мають публікуватися.", 795 + "connected": "Підключено", 796 + "paused": "Призупинено", 797 + "webhookLabel": "URL вхідного вебхука", 798 + "webhookPlaceholder": "https://hooks.slack.com/services/...", 799 + "webhookHint": "Створіть Incoming Webhook у Slack та вставте згенеровану URL-адресу сюди.", 800 + "channelLabel": "Назва каналу", 801 + "channelPlaceholder": "#team-updates", 802 + "channelHint": "Необов'язковий підпис для вашого зручності. Slack контролює фактичний канал призначення через налаштування вебхука.", 803 + "eventsTitle": "Сповіщення про події", 804 + "eventsHint": "Оберіть, які зміни в проєкті мають публікуватися в Slack.", 805 + "events": { 806 + "taskCreated": "Нові завдання", 807 + "taskStatusChanged": "Зміни статусу", 808 + "taskPriorityChanged": "Зміни пріоритету", 809 + "taskTitleChanged": "Зміни назви", 810 + "taskDescriptionChanged": "Зміни опису", 811 + "taskCommentCreated": "Нові коментарі" 812 + }, 813 + "connect": "Підключити Slack", 814 + "saveChanges": "Зберегти зміни", 815 + "update": "Оновити Slack", 816 + "disconnect": "Від'єднати" 817 + }, 818 + "discordIntegration": { 819 + "validation": { 820 + "webhookInvalid": "Введіть дійсну URL-адресу вебхука Discord" 821 + }, 822 + "toast": { 823 + "saved": "Інтеґрацію з Discord успішно збережено", 824 + "saveError": "Не вдалося зберегти інтеґрацію з Discord", 825 + "enabled": "Сповіщення Discord увімкнено", 826 + "disabled": "Сповіщення Discord призупинено", 827 + "updateError": "Не вдалося оновити інтеґрацію з Discord", 828 + "removed": "Інтеґрацію з Discord успішно видалено", 829 + "removeError": "Не вдалося видалити інтеґрацію з Discord" 830 + }, 831 + "connectionTitle": "Підключення вебхука Discord", 832 + "connectionHint": "Вставте URL вебхука Discord та оберіть, які події завдань мають публікуватися.", 833 + "connected": "Підключено", 834 + "paused": "Призупинено", 835 + "webhookLabel": "URL вебхука", 836 + "webhookPlaceholder": "https://discord.com/api/webhooks/...", 837 + "webhookHint": "Створіть вебхук каналу Discord та вставте згенеровану URL-адресу сюди.", 838 + "channelLabel": "Назва каналу", 839 + "channelPlaceholder": "#team-updates", 840 + "channelHint": "Необов'язковий підпис для вашого зручності. Discord контролює фактичний канал призначення через налаштування вебхука.", 841 + "eventsTitle": "Сповіщення про події", 842 + "eventsHint": "Оберіть, які зміни в проєкті мають публікуватися в Discord.", 843 + "events": { 844 + "taskCreated": "Нові завдання", 845 + "taskStatusChanged": "Зміни статусу", 846 + "taskPriorityChanged": "Зміни пріоритету", 847 + "taskTitleChanged": "Зміни назви", 848 + "taskDescriptionChanged": "Зміни опису", 849 + "taskCommentCreated": "Нові коментарі" 850 + }, 851 + "connect": "Підключити Discord", 852 + "saveChanges": "Зберегти зміни", 853 + "update": "Оновити Discord", 854 + "disconnect": "Від'єднати" 855 + }, 856 + "genericWebhookIntegration": { 857 + "validation": { 858 + "webhookInvalid": "Введіть дійсну URL-адресу вебхука" 859 + }, 860 + "toast": { 861 + "saved": "Загальний вебхук успішно збережено", 862 + "saveError": "Не вдалося зберегти загальний вебхук", 863 + "enabled": "Сповіщення загального вебхука увімкнено", 864 + "disabled": "Сповіщення загального вебхука призупинено", 865 + "updateError": "Не вдалося оновити загальний вебхук", 866 + "removed": "Загальний вебхук успішно видалено", 867 + "removeError": "Не вдалося видалити загальний вебхук" 868 + }, 869 + "connectionTitle": "Підключення вихідного вебхука", 870 + "connectionHint": "Надсилайте події завдань на вашу кінцеву точку у форматі JSON. Підписаний заголовок X-Kaneo-Signature включається, коли секрет налаштовано.", 871 + "connected": "Підключено", 872 + "paused": "Призупинено", 873 + "webhookLabel": "URL кінцевої точки", 874 + "webhookPlaceholder": "https://example.com/webhooks/kaneo", 875 + "webhookHint": "Kaneo надсилає POST-запити з JSON-тілом для кожної увімкненої події.", 876 + "secretLabel": "Секрет підпису", 877 + "secretPlaceholder": "Необов'язковий спільний секрет", 878 + "secretHint": "Необов'язково. Якщо вказано, Kaneo підписує тіло запиту та надсилає hex-дайджест у заголовку X-Kaneo-Signature.", 879 + "secretHintConfigured": "Секрет підпису вже налаштовано ({{secret}}). Введіть новий, щоб замінити його.", 880 + "eventsTitle": "Сповіщення про події", 881 + "eventsHint": "Оберіть, які зміни в проєкті мають ініціювати вихідні вебхуки.", 882 + "events": { 883 + "taskCreated": "Нові завдання", 884 + "taskStatusChanged": "Зміни статусу", 885 + "taskPriorityChanged": "Зміни пріоритету", 886 + "taskTitleChanged": "Зміни назви", 887 + "taskDescriptionChanged": "Зміни опису", 888 + "taskCommentCreated": "Нові коментарі" 889 + }, 890 + "connect": "Підключити вебхук", 891 + "saveChanges": "Зберегти зміни", 892 + "disconnect": "Від'єднати" 893 + }, 894 + "telegramIntegration": { 895 + "validation": { 896 + "botTokenInvalid": "Введіть дійсний токен бота Telegram", 897 + "chatIdRequired": "ID чату є обов'язковим", 898 + "threadIdInvalid": "Введіть дійсний ID теми Telegram" 899 + }, 900 + "toast": { 901 + "saved": "Інтеґрацію з Telegram успішно збережено", 902 + "saveError": "Не вдалося зберегти інтеґрацію з Telegram", 903 + "enabled": "Сповіщення Telegram увімкнено", 904 + "disabled": "Сповіщення Telegram призупинено", 905 + "updateError": "Не вдалося оновити інтеґрацію з Telegram", 906 + "removed": "Інтеґрацію з Telegram успішно видалено", 907 + "removeError": "Не вдалося видалити інтеґрацію з Telegram" 908 + }, 909 + "connectionTitle": "Підключення бота Telegram", 910 + "connectionHint": "Використовуйте токен бота Telegram та ID чату для надсилання оновлень завдань проєкту до чату або теми.", 911 + "connected": "Підключено", 912 + "paused": "Призупинено", 913 + "botTokenLabel": "Токен бота", 914 + "botTokenPlaceholder": "123456789:AAExampleBotToken", 915 + "botTokenHint": "Створіть бота через BotFather та вставте його токен сюди.", 916 + "botTokenHintConfigured": "Токен бота вже налаштовано ({{token}}). Введіть новий, щоб замінити його.", 917 + "chatIdLabel": "ID чату", 918 + "chatIdPlaceholder": "-1001234567890 або @team_updates", 919 + "chatIdHint": "Введіть ID чату Telegram або ім'я каналу, куди надсилатимуться оновлення.", 920 + "threadIdLabel": "ID теми", 921 + "threadIdPlaceholder": "Необов'язковий ID теми", 922 + "threadIdHint": "Необов'язково. Використовуйте для тем форуму всередині груп Telegram.", 923 + "chatLabelLabel": "Підпис чату", 924 + "chatLabelPlaceholder": "Оновлення розробки", 925 + "chatLabelHint": "Необов'язковий підпис для зручності всередині Kaneo.", 926 + "eventsTitle": "Сповіщення про події", 927 + "eventsHint": "Оберіть, які зміни в проєкті мають публікуватися в Telegram.", 928 + "events": { 929 + "taskCreated": "Нові завдання", 930 + "taskStatusChanged": "Зміни статусу", 931 + "taskPriorityChanged": "Зміни пріоритету", 932 + "taskTitleChanged": "Зміни назви", 933 + "taskDescriptionChanged": "Зміни опису", 934 + "taskCommentCreated": "Нові коментарі" 935 + }, 936 + "connect": "Підключити Telegram", 937 + "saveChanges": "Зберегти зміни", 938 + "disconnect": "Від'єднати" 939 + }, 940 + "repositoryBrowser": { 941 + "title": "Обрати репозиторій", 942 + "description": "Оберіть репозиторій, на якому встановлено ваш GitHub App, щоб увімкнути синхронізацію issues.", 943 + "searchPlaceholder": "Пошук репозиторіїв...", 944 + "loadError": "Не вдалося завантажити репозиторії", 945 + "tryAgain": "Спробувати ще раз", 946 + "emptyTitle": "Репозиторії не знайдено", 947 + "emptyHint": "Встановіть GitHub App на ваші репозиторії, щоб побачити їх тут.", 948 + "installGithubApp": "Встановити GitHub App", 949 + "noSearchMatchTitle": "Жоден репозиторій не відповідає вашому пошуку", 950 + "noSearchMatchHint": "Спробуйте змінити пошукові терміни або очистіть пошук, щоб побачити всі репозиторії.", 951 + "footerSummary": "{{repoCount}} репозиторіїв у {{installationCount}} встановленнях", 952 + "manageInstallations": "Керувати встановленнями", 953 + "updatedPrefix": "Оновлено", 954 + "relativeJustNow": "щойно", 955 + "relativeMinutesAgo": "{{count}} хв. тому", 956 + "relativeHoursAgo": "{{count}} год. тому", 957 + "relativeDaysAgo": "{{count}} дн. тому" 958 + }, 959 + "tasksImportExport": { 960 + "exportTasks": "Експортувати завдання", 961 + "importTasks": "Імпортувати завдання", 962 + "dialogTitle": "Імпорт завдань", 963 + "dialogDescription": "Завантажте JSON-файл із завданнями для імпорту до цього проєкту.", 964 + "expectedFormat": "Очікуваний формат:", 965 + "dropHint": "Перетягніть ваш JSON-файл сюди", 966 + "selectFile": "Обрати файл", 967 + "exporting": "Експорт завдань...", 968 + "exportSuccess": "Завдання успішно експортовано", 969 + "exportError": "Не вдалося експортувати завдання", 970 + "importing": "Імпорт завдань...", 971 + "importSuccess": "Успішно імпортовано {{count}} завдань", 972 + "importPartialError": "Не вдалося імпортувати {{count}} завдань", 973 + "importError": "Не вдалося імпортувати завдання", 974 + "invalidFormat": "Невірний формат файлу імпорту", 975 + "noFileDropped": "Файл не було перетягнуто", 976 + "notJsonFile": "Завантажте JSON-файл" 977 + }, 978 + "workflowEditor": { 979 + "loading": "Завантаження...", 980 + "createColumnsFirst": "Спочатку створіть колонки, щоб налаштувати правила автоматизації.", 981 + "githubHeading": "GitHub", 982 + "githubHint": "Коли відбувається подія GitHub, переміщувати пов'язане завдання до колонки.", 983 + "giteaHeading": "Gitea", 984 + "giteaHint": "Коли відбувається подія вебхука Gitea, переміщувати пов'язане завдання до колонки.", 985 + "selectColumnPlaceholder": "Обрати колонку...", 986 + "toastUpdated": "Правило робочого процесу оновлено", 987 + "toastError": "Не вдалося оновити правило", 988 + "events": { 989 + "branch_push": "Push гілки", 990 + "pr_opened": "PR відкрито", 991 + "pr_merged": "PR змерджено", 992 + "issue_opened": "Issue відкрито", 993 + "issue_closed": "Issue закрито" 994 + } 995 + }, 996 + "externalLinks": { 997 + "resources": "Ресурси", 998 + "issue": "Issue", 999 + "branch": "Гілка", 1000 + "merged": "Змерджено", 1001 + "draft": "Чернетка", 1002 + "open": "Відкрито" 1003 + } 1004 + }, 1005 + "navigation": { 1006 + "commandPalette": { 1007 + "suggestions": "Пропозиції", 1008 + "commands": "Команди", 1009 + "projects": "Проєкти", 1010 + "search": "Пошук", 1011 + "members": "Учасники", 1012 + "createTask": "Створити завдання", 1013 + "createProject": "Створити проєкт", 1014 + "createWorkspace": "Створити робочий простір", 1015 + "lightTheme": "Світла тема", 1016 + "darkTheme": "Темна тема", 1017 + "systemTheme": "Системна тема", 1018 + "keyboardShortcuts": "Гарячі клавіші", 1019 + "inputPlaceholder": "Пошук застосунків та команд...", 1020 + "empty": "Нічого не знайдено.", 1021 + "footer": { 1022 + "navigate": "Навігація", 1023 + "open": "Відкрити", 1024 + "close": "Закрити" 1025 + } 1026 + }, 1027 + "notifications": "Сповіщення", 1028 + "sidebar": { 1029 + "overview": "Огляд", 1030 + "projects": "Проєкти", 1031 + "members": "Учасники", 1032 + "invitations": "Запрошення", 1033 + "more": "Більше" 1034 + }, 1035 + "projectList": { 1036 + "viewProject": "Переглянути проєкт", 1037 + "shareProject": "Поділитися проєктом", 1038 + "projectSettings": "Налаштування проєкту", 1039 + "linkCopied": "Посилання на проєкт скопійовано до буфера обміну", 1040 + "addProject": "Додати проєкт", 1041 + "deleteConfirmTitle": "Видалити проєкт?", 1042 + "deleteConfirmDescription": "Це назавжди видалить проєкт та всі його дані. Цю дію неможливо скасувати.", 1043 + "deletedToast": "Проєкт видалено", 1044 + "deleteProject": "Видалити проєкт" 1045 + }, 1046 + "search": { 1047 + "inputPlaceholder": "Пошук завдань, проєктів, коментарів...", 1048 + "minCharsHint": "Введіть щонайменше 3 символи для пошуку", 1049 + "groups": { 1050 + "task": "Завдання", 1051 + "project": "Проєкти", 1052 + "workspace": "Робочі простори", 1053 + "comment": "Коментарі", 1054 + "activity": "Активність", 1055 + "fallback": "Результати" 1056 + } 1057 + }, 1058 + "settingsLayout": { 1059 + "toggleSidebar": "Перемикач бічної панелі", 1060 + "back": "Назад" 1061 + }, 1062 + "userMenu": { 1063 + "signedOutSuccess": "Вихід виконано успішно", 1064 + "signOutFailed": "Не вдалося вийти", 1065 + "unnamedUser": "Користувач", 1066 + "settings": "Налаштування", 1067 + "signingOut": "Вихід...", 1068 + "logOut": "Вийти" 1069 + }, 1070 + "workspaceSwitcher": { 1071 + "workspaces": "Робочі простори", 1072 + "switching": "Перемикання...", 1073 + "addWorkspace": "Додати робочий простір", 1074 + "selectWorkspace": "Обрати робочий простір" 1075 + }, 1076 + "page": { 1077 + "projectsTitle": "Проєкти", 1078 + "settingsTitle": "Налаштування", 1079 + "backToWorkspace": "Повернутися до робочого простору", 1080 + "settingsWorkspaceTab": "Робочий простір" 1081 + }, 1082 + "projectSettings": { 1083 + "projectLabel": "Проєкт" 1084 + }, 1085 + "keyboardShortcuts": { 1086 + "title": "Гарячі клавіші", 1087 + "subtitle": "Прискорте роботу за допомогою гарячих клавіш", 1088 + "searchPlaceholder": "Пошук гарячих клавіш...", 1089 + "footer": "Натисніть <kbd>Escape</kbd>, щоб закрити", 1090 + "categories": { 1091 + "general": "Загальні", 1092 + "create": "Створення", 1093 + "views": "Перегляди", 1094 + "navigation": "Навігація", 1095 + "quickSelect": "Швидкий вибір (у спливаючих вікнах)" 1096 + }, 1097 + "items": { 1098 + "openCommandPalette": "Відкрити командну палітру", 1099 + "globalSearch": "Глобальний пошук", 1100 + "toggleSidebar": "Перемикач бічної панелі", 1101 + "showShortcuts": "Показати гарячі клавіші", 1102 + "closeModal": "Закрити модальне/спливаюче вікно", 1103 + "createTask": "Створити завдання", 1104 + "createProject": "Створити проєкт", 1105 + "createWorkspace": "Створити робочий простір", 1106 + "boardView": "Перемкнути на вигляд дошки", 1107 + "listView": "Перемкнути на вигляд списку", 1108 + "backlogView": "Перемкнути на вигляд беклогу", 1109 + "nextTask": "Наступне завдання", 1110 + "prevTask": "Попереднє завдання", 1111 + "openTask": "Відкрити обране завдання", 1112 + "quickSelectNumber": "Обрати варіант за номером" 1113 + } 1114 + } 1115 + }, 1116 + "notifications": { 1117 + "title": "Сповіщення", 1118 + "newCount_one": "{{count}} нове", 1119 + "newCount_few": "{{count}} нових", 1120 + "newCount_many": "{{count}} нових", 1121 + "newCount_other": "{{count}} нових", 1122 + "emptyTitle": "Сповіщень ще немає", 1123 + "emptySubtitle": "Тут з'являтимуться оновлення та активність.", 1124 + "clearAll": "Очистити всі сповіщення", 1125 + "clearDialogTitle": "Очистити всі сповіщення?", 1126 + "clearDialogDescription": "Це назавжди видалить усі сповіщення. Цю дію неможливо скасувати.", 1127 + "shortcuts": { 1128 + "open": "Відкрити сповіщення" 1129 + }, 1130 + "events": { 1131 + "task_created": { 1132 + "title": "Створено нове завдання", 1133 + "content": "Завдання «{{taskTitle}}» створено" 1134 + }, 1135 + "workspace_created": { 1136 + "title": "Робочий простір створено", 1137 + "content": "Ваш робочий простір «{{workspaceName}}» успішно створено" 1138 + }, 1139 + "task_status_changed": { 1140 + "title": "Статус завдання змінено", 1141 + "content": "Статус завдання «{{taskTitle}}» змінено з «{{oldStatus}}» на «{{newStatus}}»" 1142 + }, 1143 + "task_assignee_changed": { 1144 + "title": "Завдання призначено вам", 1145 + "content": "Вас призначено на завдання: {{taskTitle}}" 1146 + }, 1147 + "time_entry_created": { 1148 + "title": "Відлік часу розпочато", 1149 + "contentWithTask": "Відлік часу розпочато для завдання: {{taskTitle}}", 1150 + "contentWithoutTask": "Відлік часу розпочато для завдання" 1151 + } 1152 + } 1153 + }, 1154 + "activity": { 1155 + "assignedToSelf": "призначив завдання собі", 1156 + "unassigned": "зняв призначення завдання", 1157 + "assignedTo": "призначив завдання на {{name}}", 1158 + "changedStatus": "змінив статус з {{from}} на {{to}}", 1159 + "changedPriority": "змінив пріоритет з {{from}} на {{to}}", 1160 + "clearedDueDate": "очистив дату завершення", 1161 + "setDueDate": "встановив дату завершення на {{date}}", 1162 + "changedDueDate": "змінив дату завершення з {{from}} на {{to}}", 1163 + "changedTitle": "змінив назву з «{{from}}» на «{{to}}»", 1164 + "githubUser": "Користувач GitHub", 1165 + "comment": { 1166 + "github": "GitHub", 1167 + "viewGithubProfile": "Переглянути профіль GitHub", 1168 + "commentedOnGithub": "прокоментував на GitHub", 1169 + "cannotBeEmpty": "Коментар не може бути порожнім", 1170 + "mustBeLoggedInToEdit": "Ви повинні увійти, щоб редагувати коментарі", 1171 + "updated": "Коментар оновлено", 1172 + "failedToUpdate": "Не вдалося оновити коментар", 1173 + "edit": "Редагувати коментар", 1174 + "editPlaceholder": "Редагувати коментар...", 1175 + "save": "Зберегти", 1176 + "added": "Коментар додано", 1177 + "failedToAdd": "Не вдалося додати коментар", 1178 + "leavePlaceholder": "Залишити коментар...", 1179 + "attachFile": "Прикріпити файл", 1180 + "submitShortcut": "Надіслати коментар", 1181 + "editor": { 1182 + "uploadsOnlyOnSavedTasks": "Завантаження файлів доступне лише для збережених завдань.", 1183 + "uploadingFile": "Завантаження файлу...", 1184 + "imageUploaded": "Зображення завантажено", 1185 + "fileAttached": "Файл прикріплено", 1186 + "failedToUploadFile": "Не вдалося завантажити файл", 1187 + "enterUrl": "Введіть URL", 1188 + "plaintext": "Звичайний текст", 1189 + "autoDetect": "Автовизначення", 1190 + "slashGroupText": "Текст", 1191 + "slashGroupLists": "Списки", 1192 + "slashGroupInsert": "Вставка", 1193 + "slashParagraph": "Текст", 1194 + "slashHeading": "Заголовок", 1195 + "slashBulletList": "Маркований список", 1196 + "slashTaskList": "Список справ", 1197 + "slashOrderedList": "Нумерований список", 1198 + "slashQuote": "Цитата", 1199 + "slashCodeBlock": "Блок коду", 1200 + "slashTable": "Таблиця", 1201 + "slashFile": "Файл", 1202 + "searchParagraph": "текст параграф звичайний", 1203 + "searchHeading": "заголовок назва h2", 1204 + "searchBulletList": "список маркований ненумерований", 1205 + "searchTaskList": "справи чеклист прапорець завдання список", 1206 + "searchOrderedList": "список нумерований", 1207 + "searchQuote": "цитата блок цитування", 1208 + "searchCodeBlock": "код фрагмент", 1209 + "searchTable": "таблиця сітка", 1210 + "searchFile": "файл вкладення зображення фото завантаження", 1211 + "embedErrorInvalidUrl": "Введіть дійсну URL-адресу", 1212 + "embedErrorYoutubeOnly": "Можна вбудовувати лише посилання YouTube.", 1213 + "embedVideo": "Вбудувати відео", 1214 + "keepAsLink": "Залишити як посилання", 1215 + "hintTab": "Tab", 1216 + "hintEsc": "Esc", 1217 + "pasteUrl": "Вставте URL", 1218 + "asLink": "Як посилання", 1219 + "embed": "Вбудувати", 1220 + "noCommands": "Немає команд", 1221 + "ariaCommentContent": "Вміст коментаря", 1222 + "ariaCommentEditor": "Редактор коментарів", 1223 + "ariaCopyCode": "Копіювати код", 1224 + "ariaCopied": "Скопійовано", 1225 + "copy": "Копіювати", 1226 + "copied": "Скопійовано", 1227 + "dropImageToUpload": "Перетягніть зображення для завантаження", 1228 + "previewImageAlt": "Попередній перегляд зображення", 1229 + "codeLang": { 1230 + "bash": "Bash", 1231 + "csharp": "C#", 1232 + "cpp": "C++", 1233 + "css": "CSS", 1234 + "go": "Golang", 1235 + "graphql": "GraphQL", 1236 + "html": "HTML", 1237 + "json": "JSON", 1238 + "java": "Java", 1239 + "javascript": "JavaScript", 1240 + "markdown": "Markdown", 1241 + "plaintext": "Звичайний текст", 1242 + "python": "Python", 1243 + "rust": "Rust", 1244 + "sql": "SQL", 1245 + "swift": "Swift", 1246 + "typescript": "TypeScript", 1247 + "yaml": "YAML" 1248 + } 1249 + } 1250 + } 1251 + }, 1252 + "tasks": { 1253 + "status": { 1254 + "label": "Статус", 1255 + "to-do": "До виконання", 1256 + "in-progress": "В роботі", 1257 + "in-review": "На перевірці", 1258 + "done": "Готово", 1259 + "archived": "Архівовано", 1260 + "planned": "Заплановано" 1261 + }, 1262 + "priority": { 1263 + "label": "Пріоритет", 1264 + "no-priority": "Без пріоритету", 1265 + "low": "Низький", 1266 + "medium": "Середній", 1267 + "high": "Високий", 1268 + "urgent": "Терміновий" 1269 + }, 1270 + "boardSearchPlaceholder": "Пошук тікетів...", 1271 + "view": { 1272 + "board": "Дошка", 1273 + "list": "Список" 1274 + }, 1275 + "common": { 1276 + "selectTask": "Обрати завдання", 1277 + "loadingTask": "Завантаження завдання..." 1278 + }, 1279 + "detail": { 1280 + "subtaskOf": "Підзавдання", 1281 + "activity": "Активність", 1282 + "noActivity": "Активність не знайдено", 1283 + "openInFullPage": "Відкрити на повну сторінку", 1284 + "titlePlaceholder": "Натисніть, щоб додати назву", 1285 + "addDescription": "Додати опис...", 1286 + "editor": { 1287 + "ariaLabel": "Редактор опису завдання", 1288 + "placeholder": "Напишіть опис...", 1289 + "previewImage": "Попередній перегляд зображення", 1290 + "enterUrl": "Введіть URL", 1291 + "autoDetect": "Автовизначення", 1292 + "copyCode": "Копіювати код", 1293 + "copy": "Копіювати", 1294 + "copied": "Скопійовано", 1295 + "attachFile": "Прикріпити файл", 1296 + "dropToUpload": "Перетягніть зображення для завантаження", 1297 + "checkbox": { 1298 + "markIncomplete": "Позначити завдання як незавершене", 1299 + "markComplete": "Позначити завдання як завершене" 1300 + }, 1301 + "upload": { 1302 + "loading": "Завантаження файлу...", 1303 + "failed": "Не вдалося завантажити файл", 1304 + "imageSuccess": "Зображення завантажено", 1305 + "fileSuccess": "Файл прикріплено" 1306 + }, 1307 + "slash": { 1308 + "groups": { 1309 + "text": "Текст", 1310 + "lists": "Списки", 1311 + "insert": "Вставка" 1312 + }, 1313 + "empty": "Немає команд", 1314 + "commands": { 1315 + "paragraph": "Текст", 1316 + "heading-2": "Заголовок", 1317 + "bullet-list": "Маркований список", 1318 + "task-list": "Список справ", 1319 + "ordered-list": "Нумерований список", 1320 + "blockquote": "Цитата", 1321 + "code-block": "Блок коду", 1322 + "table": "Таблиця", 1323 + "file": "Файл" 1324 + } 1325 + }, 1326 + "languages": { 1327 + "bash": "Bash", 1328 + "csharp": "C#", 1329 + "cpp": "C++", 1330 + "css": "CSS", 1331 + "clojure": "Clojure", 1332 + "cypher": "Cypher", 1333 + "dart": "Dart", 1334 + "diff": "Diff", 1335 + "elixir": "Elixir", 1336 + "excel": "Excel", 1337 + "go": "Golang", 1338 + "graphql": "GraphQL", 1339 + "html": "HTML", 1340 + "haskell": "Haskell", 1341 + "json": "JSON", 1342 + "java": "Java", 1343 + "javascript": "JavaScript", 1344 + "kotlin": "Kotlin", 1345 + "makefile": "Makefile", 1346 + "markdown": "Markdown", 1347 + "ocaml": "OCaml", 1348 + "php": "PHP", 1349 + "perl": "Perl", 1350 + "plaintext": "Звичайний текст", 1351 + "python": "Python", 1352 + "r": "R", 1353 + "reasonml": "ReasonML", 1354 + "ruby": "Ruby", 1355 + "rust": "Rust", 1356 + "sql": "SQL", 1357 + "swift": "Swift", 1358 + "toml": "TOML", 1359 + "terraform": "Terraform", 1360 + "typescript": "TypeScript", 1361 + "xml": "XML", 1362 + "yaml": "YAML" 1363 + }, 1364 + "embed": { 1365 + "choice": { 1366 + "embedVideo": "Вбудувати відео", 1367 + "keepAsLink": "Залишити як посилання" 1368 + }, 1369 + "inputPlaceholder": "Вставте URL", 1370 + "embeddedContent": "Вбудований вміст", 1371 + "asLink": "Як посилання", 1372 + "submit": "Вбудувати", 1373 + "errors": { 1374 + "invalidUrl": "Введіть дійсну URL-адресу", 1375 + "onlyYoutube": "Можна вбудовувати лише посилання YouTube." 1376 + }, 1377 + "onlyYoutubeInline": "Можна вбудовувати лише URL-адреси YouTube. Натомість використовуйте режим посилання." 1378 + } 1379 + } 1380 + }, 1381 + "entity": { 1382 + "task": "Завдання" 1383 + }, 1384 + "relations": { 1385 + "title": "Зв'язки", 1386 + "tasksInProject": "Завдання в проєкті", 1387 + "linkError": "Не вдалося зв'язати завдання", 1388 + "empty": "Немає пов'язаних завдань", 1389 + "searchPlaceholder": "Пошук завдань для зв'язування...", 1390 + "noTasksFound": "Завдання не знайдено", 1391 + "openTask": "Відкрити завдання", 1392 + "removeRelation": "Видалити зв'язок", 1393 + "related": "Пов'язано", 1394 + "blocks": "Блокує", 1395 + "selectTask": "Обрати завдання для зв'язування", 1396 + "types": { 1397 + "blocks": "блокує", 1398 + "related": "пов'язано з" 1399 + } 1400 + }, 1401 + "subtasks": { 1402 + "title": "Підзавдання", 1403 + "inputPlaceholder": "Назва підзавдання...", 1404 + "addAction": "Додати", 1405 + "empty": "Підзавдань ще немає", 1406 + "createError": "Не вдалося створити підзавдання", 1407 + "deleteSuccess": "Завдання успішно видалено", 1408 + "deleteError": "Не вдалося видалити завдання", 1409 + "deleteDialogTitle": "Видалити завдання?", 1410 + "deleteDialogDescription": "Це назавжди видалить завдання та всі його дані. Цю дію неможливо скасувати.", 1411 + "deleteAction": "Видалити завдання" 1412 + }, 1413 + "properties": { 1414 + "title": "Властивості", 1415 + "labels": "Мітки", 1416 + "copyTaskLink": "Копіювати посилання на завдання", 1417 + "copyTaskBranch": "Копіювати гілку завдання", 1418 + "start": "Початок", 1419 + "startDate": "Дата початку", 1420 + "noDate": "Немає дати" 1421 + }, 1422 + "move": { 1423 + "title": "Перемістити завдання", 1424 + "projectLabel": "Цільовий проєкт", 1425 + "projectPlaceholder": "Обрати проєкт", 1426 + "statusLabel": "Цільовий статус", 1427 + "statusHintKeep": "Робочий процес цього проєкту вже підтримує поточний статус.", 1428 + "statusHintAdjust": "Оберіть статус для використання в цільовому проєкті.", 1429 + "action": "Перемістити завдання", 1430 + "success": "Завдання успішно переміщено", 1431 + "error": "Не вдалося перемістити завдання" 1432 + }, 1433 + "popover": { 1434 + "assignee": { 1435 + "unassigned": "Не призначено", 1436 + "updateError": "Не вдалося оновити виконавця завдання" 1437 + }, 1438 + "status": { 1439 + "updateError": "Не вдалося оновити статус завдання" 1440 + }, 1441 + "priority": { 1442 + "updateError": "Не вдалося оновити пріоритет завдання" 1443 + }, 1444 + "dueDate": { 1445 + "updateSuccess": "Дату завершення завдання успішно оновлено", 1446 + "updateError": "Не вдалося оновити дату завершення завдання", 1447 + "clear": "Очистити дату" 1448 + }, 1449 + "startDate": { 1450 + "updateSuccess": "Дату початку завдання успішно оновлено", 1451 + "updateError": "Не вдалося оновити дату початку завдання", 1452 + "clear": "Очистити дату початку" 1453 + }, 1454 + "labels": { 1455 + "searchPlaceholder": "Пошук міток...", 1456 + "empty": "Мітки не знайдено", 1457 + "create": "Створити «{{name}}»", 1458 + "chooseColor": "Оберіть колір", 1459 + "addSuccess": "Мітку додано", 1460 + "removeSuccess": "Мітку видалено", 1461 + "updateError": "Не вдалося оновити мітку", 1462 + "createSuccess": "Мітку створено та додано", 1463 + "createError": "Не вдалося створити мітку", 1464 + "colors": { 1465 + "stone": "Камінь", 1466 + "slate": "Сланець", 1467 + "lavender": "Лаванда", 1468 + "sage": "Шавлія", 1469 + "forest": "Ліс", 1470 + "amber": "Бурштин", 1471 + "terracotta": "Теракота", 1472 + "rose": "Троянда", 1473 + "crimson": "Багряний" 1474 + } 1475 + } 1476 + }, 1477 + "backlog": { 1478 + "pageTitle": "Беклог {{name}}", 1479 + "noTasksToMove": "Немає запланованих завдань для переміщення", 1480 + "moveAllConfirm": "Перемістити всі {{count}} заплановані завдання до «До виконання»?", 1481 + "moveAllSuccess": "{{count}} завдань переміщено до «До виконання»", 1482 + "plan": "Запланувати", 1483 + "moveAllTooltip": "Перемістити всі заплановані до «До виконання»", 1484 + "moveAll": "Перемістити всі", 1485 + "addTask": "Додати завдання", 1486 + "filter": "Фільтр", 1487 + "addFilter": "Додати фільтр...", 1488 + "sections": { 1489 + "planned": "Заплановані", 1490 + "archived": "Архівовані" 1491 + }, 1492 + "noTasksInSection": "Немає завдань у розділі «{{section}}»", 1493 + "filters": { 1494 + "priority": "Пріоритет: {{name}}", 1495 + "assignee": "Виконавець: {{name}}", 1496 + "due": "Термін: {{date}}", 1497 + "label": "Мітка: {{name}}", 1498 + "dueThisWeek": "Термін цього тижня", 1499 + "dueNextWeek": "Термін наступного тижня", 1500 + "noDueDate": "Без дати завершення" 1501 + } 1502 + }, 1503 + "sort": { 1504 + "label": "Сортування", 1505 + "by": "Сортувати за", 1506 + "direction": "Напрямок", 1507 + "ascending": "За зростанням", 1508 + "descending": "За спаданням", 1509 + "fields": { 1510 + "position": "Вручну (позиція)", 1511 + "createdAt": "Дата створення", 1512 + "priority": "Пріоритет", 1513 + "dueDate": "Дата завершення", 1514 + "title": "Назва", 1515 + "number": "Номер завдання" 1516 + } 1517 + }, 1518 + "boardFilters": { 1519 + "filterBy": "Фільтрувати за", 1520 + "allStatuses": "Усі статуси", 1521 + "allPriorities": "Усі пріоритети", 1522 + "allAssignees": "Усі виконавці", 1523 + "allDueDates": "Усі дати завершення", 1524 + "allLabels": "Усі мітки", 1525 + "selectedCount": "{{count}} обрано", 1526 + "subjects": { 1527 + "status": "Статус", 1528 + "priority": "Пріоритет", 1529 + "assignee": "Виконавець", 1530 + "dueDate": "Дата завершення", 1531 + "labels": "Мітки" 1532 + }, 1533 + "operators": { 1534 + "isAnyOf": "є будь-яким з", 1535 + "includeAnyOf": "включає будь-який з" 1536 + } 1537 + }, 1538 + "gantt": { 1539 + "pageTitle": "{{name}} — Ґант", 1540 + "title": "Часова шкала Ґанта", 1541 + "searchPlaceholder": "Пошук запланованих тікетів...", 1542 + "hideTasks": "Сховати завдання", 1543 + "showTasks": "Показати завдання", 1544 + "noTasks": "Немає запланованих завдань", 1545 + "noTasksSubtitle": "Додайте дату початку, дату завершення або обидві до завдань, щоб розмістити їх на часовій шкалі проєкту.", 1546 + "noTasksFound": "Завдання не знайдено", 1547 + "noTasksMatch": "Жодне заплановане завдання не відповідає «{{query}}»", 1548 + "taskHeader": "Завдання", 1549 + "updateDatesError": "Не вдалося оновити дати завдання", 1550 + "resizeStart": "Змінити дату початку", 1551 + "resizeDue": "Змінити дату завершення", 1552 + "taskAriaLabel": "{{title}} — відкрити або перетягнути для переміщення" 1553 + }, 1554 + "delete": { 1555 + "title": "Видалити завдання?", 1556 + "description": "Це назавжди видалить завдання та всі його дані. Цю дію неможливо скасувати.", 1557 + "action": "Видалити завдання", 1558 + "success": "Завдання успішно видалено", 1559 + "error": "Не вдалося видалити завдання" 1560 + }, 1561 + "archive": { 1562 + "success": "Архівовано {{count}} завдань" 1563 + }, 1564 + "listView": { 1565 + "addTask": "Додати завдання", 1566 + "archiveAllTooltip": "Архівувати всі завершені завдання", 1567 + "noTasks": "Немає завдань" 1568 + }, 1569 + "kanban": { 1570 + "addTask": "Додати завдання" 1571 + }, 1572 + "pr": { 1573 + "merged": "Змерджено", 1574 + "draft": "Чернетка", 1575 + "open": "Відкрито", 1576 + "label": "Pull Request", 1577 + "count_one": "{{count}} PR", 1578 + "count_few": "{{count}} PR", 1579 + "count_many": "{{count}} PR", 1580 + "count_other": "{{count}} PR" 1581 + }, 1582 + "assignee": { 1583 + "label": "Виконавець", 1584 + "unassigned": "Не призначено" 1585 + }, 1586 + "dueDate": { 1587 + "label": "Дата завершення", 1588 + "clear": "Очистити дату", 1589 + "updateSuccess": "Дату завершення завдання успішно оновлено", 1590 + "updateError": "Не вдалося оновити дату завершення завдання", 1591 + "clearSuccess": "Дату завершення завдання очищено", 1592 + "clearError": "Не вдалося очистити дату завершення" 1593 + }, 1594 + "labels": { 1595 + "label": "Мітки", 1596 + "empty": "Немає доступних міток" 1597 + }, 1598 + "update": { 1599 + "success": "Завдання успішно оновлено", 1600 + "error": "Не вдалося оновити завдання" 1601 + }, 1602 + "contextMenu": { 1603 + "copyLink": "Копіювати посилання", 1604 + "copyLinkSuccess": "Посилання на завдання скопійовано!" 1605 + }, 1606 + "actions": { 1607 + "archive": "Архівувати", 1608 + "markAsPlanned": "Позначити як заплановане", 1609 + "delete": "Видалити..." 1610 + }, 1611 + "bulk": { 1612 + "selectedCount": "{{count}} обрано", 1613 + "moveToBacklog": "Перемістити до беклогу", 1614 + "moveToBacklogSuccess": "{{count}} завдань переміщено до беклогу", 1615 + "moveToBacklogError": "Не вдалося перемістити завдання до беклогу", 1616 + "moveToBoard": "Перемістити на дошку", 1617 + "moveToBoardSuccess": "{{count}} завдань переміщено на дошку", 1618 + "moveToBoardError": "Не вдалося перемістити завдання на дошку", 1619 + "delete": "Видалити завдання", 1620 + "deleteConfirm": "Видалити {{count}} завдань? Цю дію неможливо скасувати.", 1621 + "deleteSuccess": "{{count}} завдань видалено", 1622 + "deleteError": "Не вдалося видалити завдання", 1623 + "archive": "Архівувати завдання", 1624 + "archiveSuccess": "{{count}} завдань архівовано", 1625 + "archiveError": "Не вдалося архівувати завдання", 1626 + "updateSuccess": "{{count}} завдань оновлено", 1627 + "updateError": "Не вдалося оновити завдання", 1628 + "assignTo": "Призначити на", 1629 + "assignSuccess": "{{count}} завдань призначено", 1630 + "assignError": "Не вдалося призначити завдання", 1631 + "setPriority": "Встановити пріоритет", 1632 + "updatePriorityError": "Не вдалося оновити пріоритет", 1633 + "addLabel": "Додати мітку", 1634 + "addLabelSuccess": "Мітку додано до {{count}} завдань", 1635 + "addLabelError": "Не вдалося додати мітку", 1636 + "setDueDate": "Встановити дату завершення", 1637 + "updateDueDateError": "Не вдалося оновити дату завершення", 1638 + "actions": "Дії", 1639 + "searchActions": "Пошук дій...", 1640 + "noActionsFound": "Дій не знайдено.", 1641 + "changeStatus": "Змінити статус" 1642 + } 1643 + }, 1644 + "invitations": { 1645 + "pageTitle": "Запрошення", 1646 + "pendingInvitations": "Очікуючі запрошення", 1647 + "acceptSubtitle": "Прийміть запрошення, щоб приєднатися до робочих просторів", 1648 + "noPendingTitle": "Немає очікуючих запрошень", 1649 + "noPendingDescription": "Наразі у вас немає жодних очікуючих запрошень до робочих просторів.", 1650 + "continueToSetup": "Продовжити налаштування", 1651 + "skipForNow": "Пропустити поки що", 1652 + "table": { 1653 + "workspace": "Робочий простір", 1654 + "invitedBy": "Запросив", 1655 + "expires": "Закінчується" 1656 + }, 1657 + "toast": { 1658 + "acceptError": "Не вдалося прийняти запрошення", 1659 + "acceptSuccess": "Запрошення прийнято! Ласкаво просимо до команди.", 1660 + "rejectError": "Не вдалося відхилити запрошення", 1661 + "rejectSuccess": "Запрошення відхилено" 1662 + } 1663 + }, 1664 + "workspace": { 1665 + "projects": { 1666 + "pageTitle": "Проєкти", 1667 + "createProject": "Створити проєкт", 1668 + "title": "Назва", 1669 + "progress": "Прогрес", 1670 + "targetDate": "Цільова дата", 1671 + "dueDate": "Дата завершення", 1672 + "status": "Статус", 1673 + "emptyTitle": "Проєктів ще немає", 1674 + "emptyDescription": "Почніть зі створення вашого першого проєкту.", 1675 + "projectStatus": { 1676 + "notStarted": "Не розпочато", 1677 + "complete": "Завершено", 1678 + "inProgress": "В роботі" 1679 + }, 1680 + "noDueDate": "Без дати завершення" 1681 + }, 1682 + "search": { 1683 + "pageTitle": "Пошук", 1684 + "backToDashboard": "Повернутися до панелі керування", 1685 + "placeholder": "Пошук завдань за назвою або коротким ID (напр. DEP-23)...", 1686 + "hint": "Пошук по всіх проєктах у цьому робочому просторі. Використовуйте короткі ID, як DEP-23, щоб знайти конкретні завдання.", 1687 + "searching": "Пошук...", 1688 + "resultsFound_one": "{{count}} результат знайдено", 1689 + "resultsFound_few": "{{count}} результати знайдено", 1690 + "resultsFound_many": "{{count}} результатів знайдено", 1691 + "resultsFound_other": "{{count}} результатів знайдено", 1692 + "noResultsTitle": "Результатів не знайдено", 1693 + "noResultsDescription": "Спробуйте змінити пошукові терміни або пошукати щось інше", 1694 + "startTitle": "Почніть пошук", 1695 + "startDescription": "Введіть пошуковий запит, щоб знайти завдання у всіх проєктах", 1696 + "quickSearchesLabel": "Швидкий пошук:", 1697 + "suggestionHighPriority": "Високий пріоритет", 1698 + "suggestionBug": "Помилка", 1699 + "suggestionFeature": "Функція", 1700 + "suggestionInProgress": "В роботі", 1701 + "suggestionCompleted": "Завершено" 1702 + }, 1703 + "create": { 1704 + "pageTitle": "Створення робочого простору", 1705 + "heading": "Створити новий робочий простір", 1706 + "subtitle": "Робочі простори — це спільні середовища, де команди можуть працювати над проєктами, циклами та завданнями.", 1707 + "nameLabel": "Назва робочого простору", 1708 + "namePlaceholder": "Введіть назву робочого простору", 1709 + "descriptionLabel": "Опис (необов'язково)", 1710 + "descriptionPlaceholder": "Додайте опис для вашого робочого простору", 1711 + "required": "Обов'язково", 1712 + "creating": "Створення...", 1713 + "submit": "Створити робочий простір", 1714 + "success": "Робочий простір успішно створено", 1715 + "error": "Не вдалося створити робочий простір" 1716 + } 1717 + }, 1718 + "team": { 1719 + "roles": { 1720 + "owner": "Власник", 1721 + "admin": "Адміністратор", 1722 + "member": "Учасник" 1723 + }, 1724 + "members": { 1725 + "pageTitle": "Учасники", 1726 + "inviteMember": "Запросити учасника" 1727 + }, 1728 + "inviteModal": { 1729 + "title": "Запросити учасника команди", 1730 + "emailLabel": "Електронна пошта", 1731 + "emailPlaceholder": "colleague@company.com", 1732 + "sendInvitation": "Надіслати запрошення", 1733 + "success": "Запрошення успішно надіслано", 1734 + "error": "Не вдалося запросити учасника команди" 1735 + }, 1736 + "membersTable": { 1737 + "emptyTitle": "Учасників команди ще немає", 1738 + "emptyDescription": "Запросіть першого учасника команди, щоб почати.", 1739 + "columns": { 1740 + "name": "Ім'я", 1741 + "role": "Роль", 1742 + "joined": "Приєднався", 1743 + "actions": "Дії" 1744 + }, 1745 + "memberRolePending": "{{role}} (Очікує)", 1746 + "ariaCancelInvitation": "Скасувати запрошення", 1747 + "ariaRemoveMember": "Видалити учасника", 1748 + "removeDialogTitle": "Видалити учасника команди?", 1749 + "removeDialogDescription": "Ви впевнені, що хочете видалити {{name}} з робочого простору? Цю дію неможливо скасувати.", 1750 + "cancelDialogTitle": "Скасувати запрошення?", 1751 + "cancelDialogDescription": "Ви впевнені, що хочете скасувати запрошення для {{email}}? Цю дію неможливо скасувати.", 1752 + "removeMember": "Видалити учасника", 1753 + "cancelInvitation": "Скасувати запрошення", 1754 + "removeSuccess": "Учасника команди успішно видалено", 1755 + "removeError": "Не вдалося видалити учасника команди", 1756 + "cancelInviteSuccess": "Запрошення успішно скасовано", 1757 + "cancelInviteError": "Не вдалося скасувати запрошення" 1758 + } 1759 + }, 1760 + "publicProject": { 1761 + "pageTitle": "Публічний перегляд", 1762 + "badge": "Публічний", 1763 + "readOnly": "Лише для читання", 1764 + "error": { 1765 + "title": "Проєкт не знайдено", 1766 + "description": "Цей проєкт не існує або не є публічно доступним." 1767 + }, 1768 + "taskCard": { 1769 + "viewDetailsAria": "Переглянути деталі завдання {{title}}" 1770 + }, 1771 + "taskDetail": { 1772 + "labels": "Мітки", 1773 + "externalLinks": "Зовнішні посилання", 1774 + "pullRequestFallback": "Pull Request", 1775 + "issueFallback": "Issue", 1776 + "prStatusMerged": "Змерджено", 1777 + "prStatusDraft": "Чернетка", 1778 + "prStatusOpen": "Відкрито", 1779 + "dueWithDate": "Термін {{date}}", 1780 + "created": "Створено", 1781 + "dueDateLabel": "Дата завершення" 1782 + }, 1783 + "theme": { 1784 + "switchToLight": "Перемкнути на світлий режим", 1785 + "switchToDark": "Перемкнути на темний режим" 1786 + }, 1787 + "copyUrl": { 1788 + "successToast": "URL скопійовано", 1789 + "errorToast": "Не вдалося скопіювати URL", 1790 + "copied": "Скопійовано", 1791 + "share": "Поділитися" 1792 + }, 1793 + "branding": { 1794 + "poweredBy": "Працює на" 1795 + } 1796 + } 1797 + }
+2
tests/api-integration/setup.ts
··· 72 72 process.env.SMTP_SECURE = ""; 73 73 process.env.SMTP_USER = ""; 74 74 process.env.SMTP_PASSWORD = ""; 75 + process.env.GITHUB_OAUTH_CLIENT_ID = ""; 76 + process.env.GITHUB_OAUTH_CLIENT_SECRET = ""; 75 77 process.env.GITHUB_CLIENT_ID = ""; 76 78 process.env.GITHUB_CLIENT_SECRET = ""; 77 79 process.env.GOOGLE_CLIENT_ID = "";
+75
tests/api/utils/github-sso-env.test.ts
··· 1 + import { afterEach, beforeEach, describe, expect, it } from "vitest"; 2 + import { 3 + getGithubSsoOAuthCredentials, 4 + isGithubSsoConfigured, 5 + } from "../../../apps/api/src/utils/github-sso-env"; 6 + 7 + const keys = [ 8 + "GITHUB_OAUTH_CLIENT_ID", 9 + "GITHUB_OAUTH_CLIENT_SECRET", 10 + "GITHUB_CLIENT_ID", 11 + "GITHUB_CLIENT_SECRET", 12 + ] as const; 13 + 14 + describe("github-sso-env", () => { 15 + const original: Partial<Record<(typeof keys)[number], string | undefined>> = 16 + {}; 17 + 18 + beforeEach(() => { 19 + for (const key of keys) { 20 + original[key] = process.env[key]; 21 + delete process.env[key]; 22 + } 23 + }); 24 + 25 + afterEach(() => { 26 + for (const key of keys) { 27 + const value = original[key]; 28 + if (value === undefined) { 29 + delete process.env[key]; 30 + } else { 31 + process.env[key] = value; 32 + } 33 + } 34 + }); 35 + 36 + it("prefers GITHUB_OAUTH_* when both are set", () => { 37 + process.env.GITHUB_OAUTH_CLIENT_ID = "oauth-id"; 38 + process.env.GITHUB_OAUTH_CLIENT_SECRET = "oauth-secret"; 39 + process.env.GITHUB_CLIENT_ID = "legacy-id"; 40 + process.env.GITHUB_CLIENT_SECRET = "legacy-secret"; 41 + expect(getGithubSsoOAuthCredentials()).toEqual({ 42 + clientId: "oauth-id", 43 + clientSecret: "oauth-secret", 44 + }); 45 + expect(isGithubSsoConfigured()).toBe(true); 46 + }); 47 + 48 + it("falls back to legacy GITHUB_CLIENT_* when OAuth vars are unset", () => { 49 + process.env.GITHUB_CLIENT_ID = "legacy-id"; 50 + process.env.GITHUB_CLIENT_SECRET = "legacy-secret"; 51 + expect(getGithubSsoOAuthCredentials()).toEqual({ 52 + clientId: "legacy-id", 53 + clientSecret: "legacy-secret", 54 + }); 55 + expect(isGithubSsoConfigured()).toBe(true); 56 + }); 57 + 58 + it("returns empty credentials when nothing is configured", () => { 59 + expect(getGithubSsoOAuthCredentials()).toEqual({ 60 + clientId: "", 61 + clientSecret: "", 62 + }); 63 + expect(isGithubSsoConfigured()).toBe(false); 64 + }); 65 + 66 + it("treats partial OAuth pair as absent and falls back to legacy", () => { 67 + process.env.GITHUB_OAUTH_CLIENT_ID = "only-id"; 68 + process.env.GITHUB_CLIENT_ID = "legacy-id"; 69 + process.env.GITHUB_CLIENT_SECRET = "legacy-secret"; 70 + expect(getGithubSsoOAuthCredentials()).toEqual({ 71 + clientId: "legacy-id", 72 + clientSecret: "legacy-secret", 73 + }); 74 + }); 75 + });