diff --git a/README.md b/README.md index 9424f027f..7a4a57702 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ | **Workspaces** | Isolate each brand, client, or project in its own space. | | **REST API + MCP** | Full programmatic control; AI assistants integrate natively. | | **Native analytics** | Per-account reach and engagement across every connected platform. | -| **Multi-language** | English, Spanish, and Portuguese. | +| **Multi-language** | English, Ukrainian, Spanish, Portuguese, French, German, Italian, Dutch, Polish, Greek, Japanese, Korean, Chinese, Russian, Turkish, and Arabic. | ## Supported platforms diff --git a/app/Enums/Workspace/ContentLanguage.php b/app/Enums/Workspace/ContentLanguage.php index ecb7f3e24..190549af1 100644 --- a/app/Enums/Workspace/ContentLanguage.php +++ b/app/Enums/Workspace/ContentLanguage.php @@ -17,6 +17,7 @@ enum ContentLanguage: string { case English = 'en'; + case Ukrainian = 'uk'; case PortugueseBrazil = 'pt-BR'; case Spanish = 'es'; case French = 'fr'; @@ -41,6 +42,7 @@ public function label(): string { return match ($this) { self::English => 'English', + self::Ukrainian => 'Українська', self::PortugueseBrazil => 'Português (Brasil)', self::Spanish => 'Español', self::French => 'Français', @@ -66,6 +68,7 @@ public function englishName(): string { return match ($this) { self::English => 'English', + self::Ukrainian => 'Ukrainian', self::PortugueseBrazil => 'Brazilian Portuguese', self::Spanish => 'Spanish', self::French => 'French', diff --git a/config/languages.php b/config/languages.php index 37de477be..c019ef69d 100644 --- a/config/languages.php +++ b/config/languages.php @@ -17,6 +17,7 @@ 'available' => [ 'en' => 'English', + 'uk' => 'Українська', 'es' => 'Español', 'pt-BR' => 'Português', 'fr' => 'Français', diff --git a/lang/uk/accounts.php b/lang/uk/accounts.php new file mode 100644 index 000000000..a5f5b69fe --- /dev/null +++ b/lang/uk/accounts.php @@ -0,0 +1,148 @@ + 'Підключення', + 'page_title' => 'Соціальні акаунти', + 'description' => 'Огляд усіх підключених соціальних акаунтів', + 'connect_cta' => 'Підключити', + + 'not_connected' => 'Не підключено', + 'connect' => 'Підключити', + 'connection_lost' => 'З’єднання втрачено', + 'reconnect' => 'Перепідключити', + 'reconnect_account' => 'Перепідключити акаунт', + 'view_profile' => 'Переглянути профіль', + 'disconnect' => 'Від’єднати', + + 'descriptions' => [ + 'linkedin' => 'Підключіть профіль LinkedIn або сторінку компанії', + 'linkedin-page' => 'Підключіть сторінку компанії LinkedIn', + 'x' => 'Підключіть акаунт X (Twitter)', + 'tiktok' => 'Підключіть акаунт TikTok', + 'youtube' => 'Підключіть канал YouTube', + 'facebook' => 'Підключіть сторінку Facebook', + 'instagram' => 'Підключіть професійний акаунт Instagram', + 'instagram-facebook' => 'Підключіть Instagram через сторінку Facebook', + 'threads' => 'Підключіть акаунт Threads', + 'pinterest' => 'Підключіть акаунт Pinterest', + 'bluesky' => 'Підключіть акаунт Bluesky', + 'mastodon' => 'Підключіть акаунт Mastodon', + 'telegram' => 'Підключіть канал або групу Telegram', + 'discord' => 'Підключіть сервер Discord', + ], + + 'disconnect_modal' => [ + 'title' => 'Від’єднати акаунт', + 'description' => 'Ви впевнені, що хочете від’єднати цей акаунт? Ви зможете підключити його знову будь-коли.', + 'confirm' => 'Від’єднати', + 'cancel' => 'Скасувати', + ], + + 'bluesky' => [ + 'title' => 'Підключити Bluesky', + 'description' => 'Введіть облікові дані для підключення', + 'email' => 'Email', + 'email_placeholder' => 'yourhandle.bsky.social', + 'app_password' => 'Пароль додатка', + 'app_password_placeholder' => 'xxxx-xxxx-xxxx-xxxx', + 'app_password_hint' => 'З міркувань безпеки використовуйте пароль додатка. Створіть його на bsky.app/settings.', + 'submit' => 'Підключити Bluesky', + 'submitting' => 'Підключення...', + ], + + 'mastodon' => [ + 'title' => 'Підключити Mastodon', + 'description' => 'Введіть інстанс Mastodon', + 'instance_url' => 'URL інстансу', + 'instance_placeholder' => 'https://mastodon.social', + 'instance_hint' => 'Введіть URL інстансу Mastodon (напр. mastodon.social, techhub.social)', + 'submit' => 'Продовжити з Mastodon', + 'submitting' => 'Підключення...', + ], + + 'telegram' => [ + 'title' => 'Підключити Telegram', + 'description' => 'Під’єднайте канал або групу', + 'step_admin' => 'Додайте :bot як адміністратора до каналу або групи Telegram.', + 'step_command' => 'Опублікуйте цю команду в каналі або групі:', + 'waiting' => 'Очікуємо підключення каналу…', + 'connected' => 'Канал підключено!', + 'connected_toast' => 'Канал Telegram успішно підключено!', + 'copied_toast' => 'Команду скопійовано в буфер обміну', + 'copy_tooltip' => 'Копіювати команду', + 'expired' => 'Цей код прострочено. Згенеруйте новий і спробуйте ще раз.', + 'new_code' => 'Згенерувати новий код', + 'retry' => 'Спробувати ще раз', + 'error_generic' => 'Не вдалося розпочати підключення. Спробуйте ще раз.', + 'network_taken' => 'У цьому робочому просторі вже підключено канал Telegram. Спочатку від’єднайте його.', + ], + + 'facebook' => [ + 'title' => 'Виберіть сторінку Facebook', + 'description' => 'Виберіть сторінку, яку хочете підключити', + 'no_pages' => 'Сторінок не знайдено', + 'no_pages_description' => 'Ви не є адміністратором жодної сторінки Facebook.', + 'page_label' => 'Сторінка Facebook', + 'view' => 'Переглянути', + 'choose' => 'Вибрати', + ], + + 'instagram_facebook' => [ + 'title' => 'Виберіть акаунт Instagram', + 'description' => 'Виберіть акаунт Instagram, який хочете підключити', + 'no_pages' => 'Акаунтів Instagram не знайдено', + 'no_pages_description' => 'Не знайдено сторінок Facebook із підключеними бізнес-акаунтами Instagram.', + 'view' => 'Переглянути', + 'choose' => 'Вибрати', + ], + + 'linkedin' => [ + 'title' => 'Виберіть сторінку LinkedIn', + 'description' => 'Виберіть сторінку, яку хочете підключити', + 'no_pages' => 'Сторінок не знайдено', + 'no_pages_description' => 'Ви не є адміністратором жодної сторінки LinkedIn.', + 'page_label' => 'Сторінка LinkedIn', + 'select_title' => 'Де ви хочете публікувати?', + 'select_subtitle' => 'Публікуйте від свого імені або виберіть сторінку компанії, якою керуєте.', + 'person_tag' => 'Особа', + 'organization_tag' => 'Організація', + 'view' => 'Переглянути', + 'choose' => 'Вибрати', + ], + + 'flash' => [ + 'disconnected' => 'Акаунт успішно від’єднано!', + 'connected' => 'Акаунт успішно підключено!', + 'session_expired' => 'Сесію завершено. Спробуйте ще раз.', + 'workspace_not_found' => 'Робочий простір не знайдено.', + 'activated' => 'Акаунт активовано!', + 'deactivated' => 'Акаунт деактивовано!', + 'already_connected' => 'Ця платформа вже підключена.', + 'no_youtube_channels' => 'Каналів YouTube не знайдено. Спочатку створіть канал.', + ], + + 'popup_callback' => [ + 'title_success' => 'Підключено', + 'title_error' => 'Помилка', + 'closing' => 'Це вікно закриється автоматично...', + 'manual_close' => 'Ви можете закрити це вікно.', + 'popup_blocked' => 'Не вдалося відкрити вікно підключення. Дозвольте спливаючі вікна і спробуйте ще раз.', + 'connected' => 'Акаунт підключено!', + 'reconnected' => 'Акаунт перепідключено!', + 'error_connecting' => 'Помилка підключення акаунта. Спробуйте ще раз.', + 'network_taken' => 'У цьому робочому просторі вже є акаунт для цієї мережі. Спочатку від’єднайте його.', + 'error_connecting_page' => 'Помилка підключення сторінки. Спробуйте ще раз.', + 'error_connecting_channel' => 'Помилка підключення каналу. Спробуйте ще раз.', + 'session_expired' => 'Сесію завершено. Спробуйте ще раз.', + 'workspace_not_found' => 'Робочий простір не знайдено.', + 'invalid_state' => 'Недійсний стан. Спробуйте ще раз.', + 'failed_to_authenticate' => 'Не вдалося автентифікуватися.', + 'failed_to_get_profile' => 'Не вдалося отримати профіль.', + 'page_not_found' => 'Сторінку не знайдено.', + 'channel_not_found' => 'Канал не знайдено.', + 'no_facebook_pages' => 'Сторінок Facebook не знайдено. Ви маєте бути адміністратором хоча б однієї сторінки.', + 'no_facebook_instagram_pages' => 'Не знайдено сторінок Facebook із підключеними акаунтами Instagram.', + 'no_youtube_channels' => 'Каналів YouTube не знайдено. Спочатку створіть канал.', + 'not_linkedin_admin' => 'Ви не є адміністратором жодної сторінки LinkedIn.', + ], +]; diff --git a/lang/uk/analytics.php b/lang/uk/analytics.php new file mode 100644 index 000000000..1a4891b22 --- /dev/null +++ b/lang/uk/analytics.php @@ -0,0 +1,55 @@ + 'Немає підключених акаунтів з аналітикою.', + 'no_accounts_match' => 'Жоден акаунт не відповідає.', + 'search_account' => 'Пошук акаунта…', + 'select_account' => 'Виберіть акаунт, щоб переглянути аналітику.', + 'no_data' => 'Дані аналітики недоступні.', + + 'metrics' => [ + 'avg_view_duration' => 'Сер. тривалість перегляду (с)', + 'avg_view_percentage' => 'Сер. відсоток перегляду', + 'bookmarks' => 'Закладки', + 'clicks' => 'Кліки', + 'comments' => 'Коментарі', + 'custom_reaction' => 'Власна', + 'engagement' => 'Залучення', + 'favourites' => 'Обране', + 'followers' => 'Підписники', + 'following' => 'Підписки', + 'impressions' => 'Покази', + 'interactions' => 'Взаємодії', + 'likes' => 'Вподобання', + 'members' => 'Учасники', + 'minutes_watched' => 'Хвилин переглянуто', + 'organic_followers' => 'Органічні підписники', + 'outbound_clicks' => 'Зовнішні кліки', + 'page_followers' => 'Підписники сторінки', + 'page_reach' => 'Охоплення сторінки', + 'page_views' => 'Перегляди сторінки', + 'paid_followers' => 'Платні підписники', + 'pin_click_rate' => 'CTR піна', + 'pin_clicks' => 'Кліки по піну', + 'posts_engagement' => 'Залучення постів', + 'posts_reach' => 'Охоплення постів', + 'quotes' => 'Цитати', + 'reach' => 'Охоплення', + 'reblogs' => 'Репости', + 'recent_comments' => 'Нещодавні коментарі', + 'recent_likes' => 'Нещодавні вподобання', + 'recent_shares' => 'Нещодавні поширення', + 'replies' => 'Відповіді', + 'reposts' => 'Репости', + 'retweets' => 'Ретвіти', + 'saves' => 'Збереження', + 'shares' => 'Поширення', + 'subscribers' => 'Підписники', + 'subscribers_gained' => 'Отримано підписників', + 'subscribers_lost' => 'Втрачено підписників', + 'total_likes' => 'Усього вподобань', + 'video_views' => 'Перегляди відео', + 'videos' => 'Відео', + 'views' => 'Перегляди', + ], +]; diff --git a/lang/uk/assets.php b/lang/uk/assets.php new file mode 100644 index 000000000..774f7d5f9 --- /dev/null +++ b/lang/uk/assets.php @@ -0,0 +1,52 @@ + 'Медіафайли', + + 'tabs' => [ + 'my_uploads' => 'Мої завантаження', + 'stock_photos' => 'Стокові фото', + 'gifs' => 'GIF', + ], + + 'upload' => [ + 'drag_drop' => 'Перетягніть файли сюди або натисніть, щоб вибрати', + 'formats' => 'JPEG, PNG, GIF, WebP, MP4, PDF', + 'uploading' => 'Завантаження...', + 'failed' => 'Не вдалося завантажити :file. Спробуйте ще раз.', + ], + + 'empty' => [ + 'title' => 'Медіафайлів ще немає', + 'description' => 'Завантажуйте зображення та відео, щоб створити медіатеку.', + ], + + 'save_to_assets' => 'Зберегти в медіафайли', + 'saved' => 'Збережено у ваші медіафайли!', + 'create_post' => 'Створити пост', + 'add_to_post' => 'Додати до поста', + 'search_placeholder' => 'Пошук медіа...', + + 'delete' => [ + 'title' => 'Видалити медіафайл', + 'description' => 'Ви впевнені, що хочете видалити цей медіафайл? Цю дію не можна скасувати.', + 'confirm' => 'Видалити', + 'cancel' => 'Скасувати', + ], + + 'unsplash' => [ + 'search_placeholder' => 'Пошук безкоштовних фото...', + 'no_results' => 'Фото не знайдено', + 'no_results_description' => 'Спробуйте інший пошуковий запит.', + 'trending' => 'Популярне на Unsplash', + 'start_searching' => 'Шукайте безкоштовні стокові фото з Unsplash', + ], + + 'giphy' => [ + 'trending' => 'Популярне на Giphy', + 'search_placeholder' => 'Пошук GIF...', + 'no_results' => 'GIF не знайдено', + 'no_results_description' => 'Спробуйте інший пошуковий запит.', + 'powered_by' => 'Працює на GIPHY', + ], +]; diff --git a/lang/uk/auth.php b/lang/uk/auth.php new file mode 100644 index 000000000..549cd961f --- /dev/null +++ b/lang/uk/auth.php @@ -0,0 +1,144 @@ + 'Ці облікові дані не відповідають нашим записам.', + 'password' => 'Наданий пароль неправильний.', + 'throttle' => 'Забагато спроб входу. Спробуйте ще раз через :seconds сек.', + + 'flash' => [ + 'welcome' => 'Ласкаво просимо до TryPost!', + 'welcome_trial' => 'Ласкаво просимо до TryPost! Ваш пробний період розпочато.', + ], + + 'legal' => 'Продовжуючи, ви погоджуєтеся з нашими Умовами використання та Політикою конфіденційності.', + + 'slides' => [ + 'calendar' => [ + 'title' => 'Візуальний календар', + 'description' => 'Плануйте та плануйте контент за допомогою інтуїтивного календаря з перетягуванням для всіх ваших соціальних акаунтів.', + ], + 'scheduling' => [ + 'title' => 'Розумне планування', + 'description' => 'Плануйте пости в LinkedIn, X, Instagram, TikTok, YouTube та інших — усе в одному місці.', + ], + 'media' => [ + 'title' => 'Багатий медіаконтент', + 'description' => 'Публікуйте зображення, каруселі, stories та reels. Кожна платформа автоматично отримує правильний формат.', + ], + 'video' => [ + 'title' => 'Публікація відео', + 'description' => 'Завантажуйте відео один раз і публікуйте в TikTok, YouTube Shorts, Instagram Reels та Facebook Reels.', + ], + 'team' => [ + 'title' => 'Командні робочі простори', + 'description' => 'Запрошуйте команду, призначайте ролі та керуйте кількома брендами в окремих робочих просторах.', + ], + 'signatures' => [ + 'title' => 'Підписи', + 'description' => 'Зберігайте багаторазові підписи (хештеги, посилання, підписи) і додавайте їх до постів одним кліком.', + ], + ], + + 'or_continue_with' => 'Або продовжити через', + 'or_continue_with_email' => 'Або продовжити через email', + 'google_login' => 'Увійти через Google', + 'google_signup' => 'Зареєструватися через Google', + 'github_login' => 'Увійти через GitHub', + 'github_signup' => 'Зареєструватися через GitHub', + 'github_email_unavailable' => 'Не вдалося отримати ваш email з GitHub. Зробіть email публічним або надайте доступ до email, потім спробуйте ще раз.', + + 'signup_success' => [ + 'page_title' => 'Ласкаво просимо', + 'title' => 'Налаштовуємо ваш обліковий запис', + 'description' => 'Зазвичай це займає лише кілька секунд...', + ], + + 'login' => [ + 'title' => 'Увійдіть до облікового запису', + 'description' => 'Введіть email і пароль нижче, щоб увійти', + 'page_title' => 'Вхід', + 'email' => 'Адреса email', + 'password' => 'Пароль', + 'forgot_password' => 'Забули пароль?', + 'remember_me' => 'Запам’ятати мене', + 'submit' => 'Увійти', + 'no_account' => 'Немає облікового запису?', + 'sign_up' => 'Зареєструватися', + ], + + 'register' => [ + 'title' => 'Увесь ваш соціальний календар в одному місці', + 'description' => 'Створіть обліковий запис і почніть планувати пости в усіх мережах.', + 'page_title' => 'Реєстрація', + 'signup_with_email' => 'Зареєструватися через email', + 'name' => 'Ім’я', + 'name_placeholder' => 'Повне ім’я', + 'email' => 'Адреса email', + 'password' => 'Пароль', + 'show_password' => 'Показати пароль', + 'hide_password' => 'Приховати пароль', + 'submit' => 'Створити обліковий запис', + 'has_account' => 'Уже маєте обліковий запис?', + 'log_in' => 'Увійти', + ], + + 'forgot_password' => [ + 'title' => 'Забули пароль', + 'description' => 'Введіть email, щоб отримати посилання для скидання пароля', + 'page_title' => 'Забули пароль', + 'email' => 'Адреса email', + 'submit' => 'Надіслати посилання для скидання', + 'return_to' => 'Або повернутися до', + 'log_in' => 'входу', + ], + + 'reset_password' => [ + 'title' => 'Скинути пароль', + 'description' => 'Введіть новий пароль нижче', + 'page_title' => 'Скинути пароль', + 'email' => 'Email', + 'password' => 'Пароль', + 'confirm_password' => 'Підтвердіть пароль', + 'confirm_placeholder' => 'Підтвердіть пароль', + 'submit' => 'Скинути пароль', + ], + + 'verify_email' => [ + 'title' => 'Підтвердіть email', + 'description' => 'Підтвердіть адресу email, натиснувши на посилання, яке ми щойно надіслали.', + 'page_title' => 'Підтвердження email', + 'link_sent' => 'Нове посилання для підтвердження надіслано на email, який ви вказали під час реєстрації.', + 'resend' => 'Надіслати лист підтвердження ще раз', + 'log_out' => 'Вийти', + ], + + 'accept_invite' => [ + 'page_title' => 'Прийняти запрошення', + 'title' => 'Вас запросили!', + 'description' => 'Вас запросили приєднатися до робочого простору :workspace.', + 'workspace' => 'Робочий простір', + 'your_role' => 'Ваша роль', + 'email' => 'Email', + 'accept' => 'Прийняти запрошення', + 'decline' => 'Відхилити запрошення', + 'login_prompt' => 'Увійдіть або створіть обліковий запис, щоб прийняти це запрошення.', + 'log_in' => 'Увійти', + 'create_account' => 'Створити обліковий запис', + 'expired_title' => 'Це запрошення більше не дійсне', + 'expired_description' => 'Робочий простір для цього запрошення було видалено. Попросіть власника облікового запису надіслати нове запрошення, якщо вам ще потрібен доступ.', + 'expired_action' => 'На головну', + ], + +]; diff --git a/lang/uk/automations.php b/lang/uk/automations.php new file mode 100644 index 000000000..4e1f8df73 --- /dev/null +++ b/lang/uk/automations.php @@ -0,0 +1,413 @@ + 'Редактор автоматизацій найкраще працює на великому екрані. Відкрийте його на комп’ютері, щоб побудувати workflow.', + 'title' => 'Автоматизації', + 'default_name' => 'Нова автоматизація', + + 'actions' => [ + 'new' => 'Нова автоматизація', + 'edit' => 'Редагувати', + 'save' => 'Зберегти', + 'activate' => 'Активувати', + 'pause' => 'Призупинити', + 'delete' => 'Видалити', + 'retry' => 'Повторити', + 'guide' => 'Дізнатися, як це працює', + ], + + 'tabs' => [ + 'build' => 'Конструктор', + 'variables' => 'Змінні', + 'test' => 'Тест', + ], + + 'nav' => [ + 'workflow' => 'Процес', + 'invocations' => 'Запуски', + 'metrics' => 'Метрики', + 'settings' => 'Налаштування', + ], + + 'settings' => [ + 'general' => 'Загальні', + 'general_description' => 'Перейменуйте цю автоматизацію.', + 'name_label' => 'Назва', + 'name_saved' => 'Автоматизацію перейменовано.', + 'status_title' => 'Статус', + 'status_description' => 'Активуйте, щоб запустити, або призупиніть, щоб зупинити.', + 'activated_at' => 'Активовано :date', + 'paused_at' => 'Призупинено :date', + 'created_at' => 'Створено :date', + 'danger_title' => 'Небезпечна зона', + 'danger_description' => 'Незворотні дії.', + 'delete_title' => 'Видалити цю автоматизацію', + 'delete_description' => 'Назавжди видаляє автоматизацію та історію її запусків.', + ], + + 'status_run' => [ + 'pending' => 'Очікує', + 'running' => 'Виконується', + 'waiting' => 'Очікування', + 'completed' => 'Завершено', + 'failed' => 'Помилка', + 'cancelled' => 'Скасовано', + ], + + 'node_type' => [ + 'trigger' => 'Тригер', + 'generate' => 'Генерація контенту', + 'delay' => 'Затримка', + 'condition' => 'Умова', + 'publish' => 'Публікація', + 'webhook' => 'Webhook', + 'end' => 'Кінець', + 'fetch_rss' => 'Отримати RSS', + 'http_request' => 'HTTP-запит', + ], + + 'invocations' => [ + 'empty' => 'Запусків ще немає.', + 'refresh' => 'Оновити', + 'search_placeholder' => 'Пошук за ID запуску…', + 'copied' => 'ID запуску скопійовано.', + 'loading' => 'Завантаження кроків…', + 'no_steps' => 'Кроки не записані.', + 'load_error' => 'Не вдалося завантажити кроки.', + 'steps' => '{0}Немає кроків|{1}:count крок|[2,*]:count кроки', + 'filter' => [ + 'all' => 'Усі статуси', + ], + 'columns' => [ + 'timestamp' => 'Час', + 'run' => 'Запуск', + 'status' => 'Статус', + 'message' => 'Останнє повідомлення', + 'duration' => 'Тривалість', + ], + 'summary' => [ + 'completed' => 'Процес завершено', + 'failed' => 'Процес завершився з помилкою', + 'running' => 'Процес виконується', + 'cancelled' => 'Процес скасовано', + 'pending' => 'Процес очікує', + ], + ], + + 'metrics' => [ + 'overview' => 'Огляд', + 'runs_over_time' => 'Запуски з часом', + 'posts_by_platform' => 'Пости за платформами', + 'no_posts' => 'За цей період не опубліковано жодного поста.', + 'cards' => [ + 'runs' => 'Усього запусків', + 'completed' => 'Завершено', + 'failed' => 'З помилкою', + 'in_progress' => 'В процесі', + 'success_rate' => 'Частка успішних', + 'avg_duration' => 'Сер. тривалість', + 'posts_created' => 'Створено постів', + ], + 'legend' => [ + 'started' => 'Розпочато', + 'completed' => 'Завершено', + 'failed' => 'З помилкою', + ], + ], + + 'categories' => [ + 'sources' => 'Джерела', + 'content' => 'Контент', + 'flow' => 'Логіка', + 'output' => 'Вивід', + ], + + 'variables' => [ + 'title' => 'Змінні процесу', + 'hint' => 'Багаторазові значення, доступні скрізь через {{ variables.KEY }}. Зберігаються в зашифрованому вигляді.', + 'empty' => 'Змінних ще немає.', + 'key' => 'Ключ', + 'value' => 'Значення', + 'key_placeholder' => 'API_KEY', + 'value_placeholder' => 'Значення', + 'add' => 'Нова змінна', + ], + + 'expr' => [ + 'trigger_event' => 'Назва події тригера', + 'trigger_fired_at' => 'Коли спрацював тригер', + 'trigger_post_id' => 'ID поста-тригера', + 'trigger_post_content' => 'Вміст поста-тригера', + 'trigger_post_status' => 'Статус поста-тригера', + 'trigger_post_scheduled_at' => 'Коли пост заплановано', + 'trigger_post_published_at' => 'Коли пост опубліковано', + 'fetched_title' => 'Заголовок отриманого елемента', + 'fetched_link' => 'Посилання отриманого елемента', + 'fetched_date' => 'Дата публікації отриманого елемента', + 'fetched_content' => 'Повний вміст отриманого елемента', + 'fetched_description' => 'Короткий опис отриманого елемента', + 'fetched_author' => 'Автор отриманого елемента', + 'fetched_image' => 'URL зображення отриманого елемента', + 'fetched_categories' => 'Категорії отриманого елемента', + 'fetched_enclosure' => 'Медіа отриманого елемента (аудіо/відео/файл)', + 'fetched_pubdate' => 'Дата публікації отриманого елемента', + 'fetched_http' => 'Отриманий HTTP-елемент (додайте поле)', + 'generated_content' => 'AI-згенерований вміст поста', + 'generated_post_url' => 'URL AI-згенерованого поста', + 'variable' => 'Змінна процесу', + 'now' => 'Поточна дата та час', + ], + + 'test' => [ + 'description' => 'Виконує автоматизацію від початку до кінця, використовуючи синтезовані дані тригера. Корисно для перевірки кожного вузла без очікування реального розкладу або стрічки.', + 'starting' => 'Запуск тестового прогону…', + 'in_progress' => 'В процесі', + 'completed' => 'Завершено', + 'failed' => 'Помилка', + 'waiting' => 'Очікування', + 'close' => 'Закрити', + 'no_node_runs' => 'Очікування запуску першого вузла…', + 'node_input' => 'Вхід', + 'node_output' => 'Вихід', + 'node_error' => 'Помилка', + 'no_new_items' => 'Немає нових елементів — наступні вузли не запускалися.', + 'error_starting' => 'Не вдалося запустити тестовий прогін.', + 'with_real_data' => 'З реальними даними', + 'run' => 'Запустити тест', + 'idle_hint' => 'Натисніть «Запустити тест», щоб виконати автоматизацію від початку до кінця.', + 'real_data_hint' => 'Цей тест опублікує пости, оновить мітки опитування та викличе зовнішні побічні ефекти.', + 'dry_badge' => 'Тестовий прогін', + ], + + 'status' => [ + 'draft' => 'Чернетка', + 'active' => 'Активна', + 'paused' => 'Призупинена', + ], + + 'index' => [ + 'empty_title' => 'Автоматизацій ще немає', + 'empty_description' => 'Створіть першу автоматизацію, щоб публікувати на автопілоті.', + 'columns' => [ + 'name' => 'Назва', + 'status' => 'Статус', + 'created' => 'Створено', + ], + ], + + 'form' => [ + 'activate_error_fallback' => 'Не вдалося активувати автоматизацію.', + 'pause_error_fallback' => 'Не вдалося призупинити автоматизацію.', + 'save_error_fallback' => 'Не вдалося зберегти автоматизацію.', + 'save_success' => 'Автоматизацію збережено.', + 'empty_canvas_title' => 'Почніть створювати автоматизацію', + 'empty_canvas_description' => 'Перетягніть вузол з лівої панелі, щоб почати.', + 'name_placeholder' => 'Автоматизація без назви', + ], + + 'nodes' => [ + 'trigger' => 'Тригер', + 'generate' => 'Генерація', + 'delay' => 'Затримка', + 'condition' => 'Умова', + 'publish' => 'Публікація', + 'webhook' => 'Webhook', + 'end' => 'Кінець', + 'end_summary' => 'Зупиняє автоматизацію тут', + 'fetch_rss' => 'Отримати RSS', + 'http_request' => 'HTTP-запит', + 'handles' => [ + 'items' => 'є елементи', + 'no_items' => 'немає елементів', + ], + ], + + 'config' => [ + 'select_placeholder' => 'Виберіть…', + 'invalid_json' => 'Це ще не коректний JSON.', + 'expand_editor' => 'Розгорнути редактор', + 'minimize_editor' => 'Згорнути', + + 'trigger' => [ + 'type' => 'Тип тригера', + 'types' => [ + 'schedule' => 'Розклад', + 'post_published' => 'Коли пост опубліковано', + 'post_scheduled' => 'Коли пост заплановано', + ], + 'post_published_hint' => 'Запускається щоразу, коли будь-який пост у цьому робочому просторі опубліковано. Опублікований пост доступний у {{ trigger.post }} для наступних вузлів.', + 'post_scheduled_hint' => 'Запускається щоразу, коли будь-який пост у цьому робочому просторі заплановано. Запланований пост доступний у {{ trigger.post }}.', + + 'schedule' => [ + 'field' => 'Інтервал спрацювання', + 'fields' => [ + 'minutes' => 'Хвилини', + 'hours' => 'Години', + 'days' => 'Дні', + 'weeks' => 'Тижні', + 'months' => 'Місяці', + ], + 'minutes_interval' => 'Хвилин між спрацюваннями', + 'hours_interval' => 'Годин між спрацюваннями', + 'days_interval' => 'Днів між спрацюваннями', + 'hour' => 'Спрацювання о годині', + 'minute' => 'Спрацювання на хвилині', + 'weekdays' => 'Спрацювання у будні', + 'day_of_month' => 'День місяця', + 'weekday_names' => [ + 'sun' => 'Нд', + 'mon' => 'Пн', + 'tue' => 'Вт', + 'wed' => 'Ср', + 'thu' => 'Чт', + 'fri' => 'Пт', + 'sat' => 'Сб', + ], + 'summary' => [ + 'every_n_minutes' => 'Запускається щохвилини|Запускається кожні :count хвилини|Запускається кожні :count хвилин', + 'every_n_hours' => 'Запускається щогодини на :minute-й хвилині|Запускається кожні :count години на :minute-й хвилині|Запускається кожні :count годин на :minute-й хвилині', + 'every_n_days' => 'Запускається щодня о :time|Запускається кожні :count дні о :time|Запускається кожні :count днів о :time', + 'weekly' => 'Запускається у :days о :time', + 'monthly' => 'Запускається :day числа кожного місяця о :time', + ], + ], + ], + 'generate' => [ + 'social_accounts' => 'Соціальні акаунти', + 'social_accounts_empty' => 'Немає підключених соціальних акаунтів. Спочатку підключіть один.', + 'target_slide_count' => 'Слайдів для генерації', + 'prompt_template' => 'Шаблон промпту', + 'prompt_template_hint' => 'Введіть {{, щоб вставити дані з попередніх кроків.', + 'image_count' => 'Зображень для генерації', + 'image_count_hint' => '0 = лише текст (без зображення). 1 = одне зображення. 2+ = карусель.', + 'use_brand_voice' => 'Використовувати голос бренду', + 'use_brand_voice_hint' => 'Застосовуйте опис і голос вашого бренду. Вимкніть для точної курації сторонніх джерел (новини, RSS).', + 'use_brand_visuals' => 'Використовувати візуал бренду', + 'use_brand_visuals_hint' => 'Спрямовуйте AI-зображення кольорами та айдентикою бренду. Вимкніть для нейтральних зображень лише за темою поста.', + 'style' => 'Стиль', + 'account_summary' => ':count акаунт · :format|:count акаунти · :format|:count акаунтів · :format', + 'formats' => [ + 'single' => 'один', + 'carousel' => 'карусель', + ], + ], + 'delay' => [ + 'duration' => 'Тривалість', + 'unit' => 'Одиниця', + 'units' => [ + 'minutes' => 'Хвилини', + 'hours' => 'Години', + 'days' => 'Дні', + ], + ], + 'condition' => [ + 'field' => 'Поле', + 'operator' => 'Оператор', + 'operators' => [ + 'contains' => 'містить', + 'not_contains' => 'не містить', + 'equals' => 'дорівнює', + 'not_equals' => 'не дорівнює', + 'matches' => 'відповідає (regex)', + 'greater_than' => 'більше ніж', + 'less_than' => 'менше ніж', + ], + 'value' => 'Значення', + ], + 'publish' => [ + 'mode' => 'Режим', + 'modes' => [ + 'now' => 'Опублікувати зараз', + 'scheduled' => 'Запланувати', + 'draft' => 'Зберегти як чернетку', + ], + 'scheduled_offset' => 'Зміщення від тригера (хвилини)', + 'offset_summary' => ':mode · +:offset хв', + ], + 'webhook' => [ + 'url' => 'URL', + 'method' => 'Метод', + 'payload_template' => 'Шаблон payload (JSON)', + ], + 'end' => [ + 'reason' => 'Причина (необов’язково)', + 'reason_placeholder' => 'напр. Відфільтровано умовою', + ], + 'fetch_rss' => [ + 'feed_url' => 'URL стрічки', + 'feed_url_hint' => 'При першому запуску мітку встановлюють на «зараз», щоб історичні елементи не заповнили наступні вузли. Наступні запуски бачать лише елементи новіші за попереднє опитування.', + 'inspect' => 'Перевірити стрічку', + 'inspecting' => 'Перевірка…', + 'inspect_hint' => 'Отримайте зразок, щоб дізнатися доступні поля для наступних вузлів.', + 'inspect_error' => 'Не вдалося прочитати цю стрічку. Перевірте URL і спробуйте ще раз.', + 'discovered_fields' => 'Доступні поля', + 'discovered_empty' => 'У останньому елементі поля не знайдено.', + ], + 'http_request' => [ + 'url' => 'URL', + 'method' => 'Метод', + 'auth_type' => 'Автентифікація', + 'auth' => [ + 'none' => 'Немає (публічний)', + 'bearer' => 'Bearer-токен', + 'basic' => 'Basic-автентифікація', + 'api_key' => 'Заголовок з API-ключем', + ], + 'bearer_token' => 'Bearer-токен', + 'basic_username' => 'Ім’я користувача', + 'basic_password' => 'Пароль', + 'api_key_header' => 'Назва заголовка', + 'api_key_value' => 'API-ключ', + 'body_template' => 'Шаблон тіла (JSON)', + 'headers' => 'Заголовки', + 'header_name' => 'Назва заголовка', + 'header_value' => 'Значення', + 'add_header' => 'Додати заголовок', + 'polling_section' => 'Список і дедуплікація (необов’язково)', + 'polling_hint' => 'Якщо відповідь — список, кожен елемент запускає процес окремо. Один об’єкт запускає його один раз.', + 'items_path' => 'Шлях до елементів', + 'items_path_hint' => 'Залиште порожнім, якщо відповідь уже масив. Використовуйте шлях через крапку (напр. data.items) для вкладеного масиву або * для об’єкта з ключами за id.', + 'item_key_path' => 'Шлях до ключа елемента', + 'item_key_path_hint' => 'JSON-шлях до унікального id (напр. id). Вже бачені елементи пропускаються, тож стрічка без дат все одно передає лише нові записи.', + 'item_date_path' => 'Шлях до дати елемента', + 'item_date_path_hint' => 'JSON-шлях до мітки часу елемента (напр. published_at). Переважніший за шлях ключа, коли доступний. Перше опитування фіксує базову мітку і нічого не передає, тож існуюча стрічка не заповнить систему в перший день.', + ], + ], + + 'delete' => [ + 'title' => 'Видалити автоматизацію', + 'description' => 'Ви впевнені, що хочете видалити цю автоматизацію? Усі запуски та елементи тригерів також буде видалено. Цю дію не можна скасувати.', + 'confirm' => 'Видалити', + 'cancel' => 'Скасувати', + ], + + 'flash' => [ + 'deleted' => 'Автоматизацію успішно видалено!', + ], + + 'errors' => [ + 'no_active_social_accounts' => 'Для цієї автоматизації не налаштовано активних соціальних акаунтів.', + 'must_have_one_trigger' => 'Автоматизація має містити рівно один вузол-тригер.', + 'trigger_must_be_connected' => 'Вузол-тригер має бути з’єднаний хоча б з одним вузлом.', + 'graph_contains_cycle' => 'Граф автоматизації містить цикл.', + 'only_failed_can_retry' => 'Повторити можна лише запуски з помилкою.', + 'no_generated_post' => 'У запуску не знайдено згенерованого поста.', + 'webhook_server_error' => 'Помилка сервера webhook.', + 'webhook_request_failed' => 'Не вдалося виконати запит webhook.', + 'webhook_missing_url' => 'У вузлі webhook відсутній URL.', + 'webhook_invalid_payload_json' => 'Шаблон payload не є коректним JSON.', + 'url_not_allowed' => 'URL запиту вказує на приватну або недоступну адресу і було заблоковано.', + 'node_no_longer_exists' => 'Вузол :node_id більше не існує в автоматизації.', + 'no_trigger_connection' => 'До вузла-тригера не підключено жодного вузла.', + 'fetch_rss_missing_url' => 'У вузлі «Отримати RSS» відсутній URL стрічки.', + 'fetch_rss_request_failed' => 'Запит RSS-стрічки не вдався.', + 'fetch_rss_malformed' => 'RSS-стрічка має некоректний формат.', + 'http_missing_url' => 'У вузлі HTTP-запиту відсутній URL.', + 'http_request_exception' => 'HTTP-запит викинув виняток.', + 'http_request_failed' => 'HTTP-запит не вдався.', + 'http_items_path_not_array' => 'Шлях до елементів не повернув список.', + 'generate_image_format_required' => 'AI генерує лише зображення. Виберіть формат зображення (не відео).', + ], +]; diff --git a/lang/uk/billing.php b/lang/uk/billing.php new file mode 100644 index 000000000..055302ffd --- /dev/null +++ b/lang/uk/billing.php @@ -0,0 +1,76 @@ + 'Оплата', + + 'past_due_notice' => [ + 'title' => 'Прострочений платіж', + 'description' => 'Оновіть спосіб оплати, щоб зберегти активну підписку.', + 'cta' => 'Оновити оплату', + ], + + 'annual_banner' => [ + 'title' => '2 місяці безкоштовно', + 'description' => 'Перейдіть на річну оплату та платіть менше щомісяця — той самий план, нічого більше не змінюється.', + 'cta' => 'Перейти на річну оплату', + ], + + 'subscribe' => [ + 'billed_monthly' => 'Щомісячна оплата', + 'billed_yearly' => 'Річна оплата', + 'prices' => [ + 'workspace' => ['monthly' => '$12', 'yearly_per_month' => '$10', 'yearly' => '$120'], + ], + ], + + 'plan' => [ + 'title' => 'План', + 'description' => 'Керуйте своїм тарифним планом.', + 'label' => 'План', + 'workspaces' => '{1}:count робочий простір|[2,*]:count робочих просторів', + 'per_workspace' => 'за робочий простір', + 'price' => 'Ціна', + 'month' => 'місяць', + 'trial' => 'Пробний період', + 'active' => 'Активний', + 'past_due' => 'Прострочено', + 'cancelling' => 'Скасовується', + 'trial_ends' => 'Пробний період закінчується', + ], + + 'subscription' => [ + 'title' => 'Підписка', + 'description' => 'Керуйте способом оплати, реквізитами та підпискою.', + 'payment_method' => 'Спосіб оплати', + 'no_payment_method' => 'Спосіб оплати ще не додано.', + 'expires_on' => 'Діє до :month/:year', + 'manage_label' => 'Підписка', + 'manage_stripe' => 'Керувати в Stripe', + ], + + 'invoices' => [ + 'title' => 'Рахунки', + 'description' => 'Завантажуйте минулі рахунки.', + 'empty' => 'Рахунків не знайдено', + 'paid' => 'Оплачено', + ], + + 'flash' => [ + 'plan_changed' => 'Ви перейшли на план :plan.', + 'switched_to_yearly' => 'Тепер у вас річна оплата.', + 'cannot_manage' => 'Лише власник облікового запису може керувати оплатою.', + 'credits_exhausted' => 'AI-кредити вичерпано — ваш місячний ліміт :limit використано. Оновіть план або зачекайте до наступного місяця.', + 'subscription_required' => 'Для використання AI-функцій потрібна активна підписка.', + ], + + 'processing' => [ + 'page_title' => 'Обробка...', + 'title' => 'Обробка вашої підписки', + 'description' => 'Зачекайте, поки ми налаштуємо ваш обліковий запис. Це займе лише мить.', + 'success_title' => 'Усе готово!', + 'success_description' => 'Ваша підписка активна. Перенаправляємо до ваших робочих просторів...', + 'cancelled_title' => 'Оформлення скасовано', + 'cancelled_description' => 'Оформлення підписки скасовано. Кошти не списано.', + 'retry' => 'Спробувати ще раз', + ], +]; diff --git a/lang/uk/brands.php b/lang/uk/brands.php new file mode 100644 index 000000000..9946a5a29 --- /dev/null +++ b/lang/uk/brands.php @@ -0,0 +1,39 @@ + 'Новий бренд', + 'no_brands_yet' => 'Брендів ще немає', + 'no_brands_description' => 'Створюйте бренди, щоб організовувати соціальні акаунти за клієнтами або проєктами', + 'accounts_count' => ':count акаунтів', + + 'create' => [ + 'title' => 'Створити бренд', + 'description' => 'Дайте бренду назву для групування соціальних акаунтів', + 'name' => 'Назва бренду', + 'name_placeholder' => 'напр. Acme Corp, Особистий', + 'submit' => 'Створити бренд', + 'submitting' => 'Створення...', + ], + + 'edit' => [ + 'title' => 'Редагувати бренд', + 'description' => 'Оновіть назву цього бренду', + 'name' => 'Назва бренду', + 'name_placeholder' => 'напр. Acme Corp, Особистий', + 'submit' => 'Зберегти зміни', + 'submitting' => 'Збереження...', + ], + + 'delete' => [ + 'title' => 'Видалити бренд', + 'description' => 'Ви впевнені, що хочете видалити цей бренд? Соціальні акаунти буде від’єднано від бренду, але не видалено.', + 'confirm' => 'Видалити', + 'cancel' => 'Скасувати', + ], + + 'flash' => [ + 'created' => 'Бренд успішно створено!', + 'updated' => 'Бренд успішно оновлено!', + 'deleted' => 'Бренд успішно видалено!', + ], +]; diff --git a/lang/uk/calendar.php b/lang/uk/calendar.php new file mode 100644 index 000000000..fd5ecdb45 --- /dev/null +++ b/lang/uk/calendar.php @@ -0,0 +1,12 @@ + 'Календар', + 'today' => 'Сьогодні', + 'day' => 'День', + 'week' => 'Тиждень', + 'month' => 'Місяць', + 'new_post' => 'Новий пост', + 'no_content' => 'Немає контенту', + 'more' => '+:count ще', +]; diff --git a/lang/uk/comments.php b/lang/uk/comments.php new file mode 100644 index 000000000..ea942190a --- /dev/null +++ b/lang/uk/comments.php @@ -0,0 +1,18 @@ + 'Напишіть коментар...', + 'reply_placeholder' => 'Напишіть відповідь...', + 'reply' => 'Відповісти', + 'edit' => 'Редагувати', + 'delete' => 'Видалити', + 'edited' => 'змінено', + 'save' => 'Зберегти', + 'cancel' => 'Скасувати', + 'send' => 'Надіслати', + 'replying_to' => 'Відповідь для :name', + 'empty' => 'Коментарів ще немає. Почніть розмову.', + 'load_more' => 'Завантажити старіші коментарі', + 'today' => 'Сьогодні', + 'yesterday' => 'Вчора', +]; diff --git a/lang/uk/common.php b/lang/uk/common.php new file mode 100644 index 000000000..07dbb68b5 --- /dev/null +++ b/lang/uk/common.php @@ -0,0 +1,67 @@ + 'Назад', + + 'beta' => 'Бета', + + 'confirm_modal' => [ + 'cannot_be_undone' => 'Цю дію не можна скасувати.', + 'type' => 'Введіть', + 'to_confirm' => 'для підтвердження.', + 'copy_to_clipboard' => 'Копіювати в буфер обміну', + 'delete_keyword' => 'delete', + ], + + 'photo_upload' => [ + 'upload' => 'Завантажити', + 'uploading' => 'Завантаження...', + 'remove' => 'Видалити фото', + 'hint' => 'Рекомендовано: квадратне зображення, макс. 2 МБ.', + 'crop_title' => 'Обрізати зображення', + 'crop_description' => 'Виберіть область, яку хочете залишити.', + 'crop_hint' => 'Перетягніть, щоб перемістити, або за кут — щоб змінити розмір.', + 'crop_save' => 'Зберегти', + 'crop_cancel' => 'Скасувати', + 'crop_error' => 'Не вдалося завантажити це зображення. Спробуйте інший файл.', + ], + + 'timezone' => [ + 'select' => 'Виберіть часовий пояс', + 'search' => 'Пошук часового поясу...', + 'empty' => 'Часовий пояс не знайдено', + ], + + 'date_picker' => [ + 'select' => 'Виберіть дату', + ], + + 'date_range_picker' => [ + 'placeholder' => 'Виберіть діапазон дат', + 'today' => 'Сьогодні', + 'yesterday' => 'Вчора', + 'last_7_days' => 'Останні 7 днів', + 'last_30_days' => 'Останні 30 днів', + 'last_3_months' => 'Останні 3 місяці', + 'last_6_months' => 'Останні 6 місяців', + 'last_12_months' => 'Останні 12 місяців', + 'this_month' => 'Цей місяць', + 'last_month' => 'Минулий місяць', + 'year_to_date' => 'З початку року', + 'last_year' => 'Минулий рік', + ], + + 'cancel' => 'Скасувати', + 'clear' => 'Очистити', + 'close' => 'Закрити', + 'loading_more' => 'Завантаження...', + + 'actions' => [ + 'copy' => 'Копіювати', + 'copied' => 'Скопійовано', + 'copy_failed' => 'Не вдалося скопіювати в буфер обміну', + ], +]; diff --git a/lang/uk/labels.php b/lang/uk/labels.php new file mode 100644 index 000000000..8ed2b2605 --- /dev/null +++ b/lang/uk/labels.php @@ -0,0 +1,54 @@ + 'Мітки', + 'description' => 'Створюйте мітки для організації та категоризації постів', + 'search' => 'Пошук міток...', + 'new_label' => 'Нова мітка', + 'no_labels_yet' => 'Міток ще немає', + 'no_search_results' => 'Жодна мітка не відповідає вашому пошуку', + 'try_different_search' => 'Спробуйте інше ключове слово або очистіть пошук.', + 'create_first_label' => 'Створіть свою першу мітку', + 'table' => [ + 'name' => 'Назва', + 'created_at' => 'Створено', + ], + + 'actions' => [ + 'edit' => 'Редагувати мітку', + 'delete' => 'Видалити мітку', + ], + + 'create' => [ + 'title' => 'Створити мітку', + 'description' => 'Дайте мітці назву та виберіть колір', + 'name' => 'Назва', + 'name_placeholder' => 'Введіть назву мітки...', + 'color' => 'Колір', + 'submit' => 'Створити мітку', + 'submitting' => 'Створення...', + ], + + 'edit' => [ + 'title' => 'Редагувати мітку', + 'description' => 'Оновіть назву та колір цієї мітки', + 'name' => 'Назва', + 'name_placeholder' => 'Введіть назву мітки...', + 'color' => 'Колір', + 'submit' => 'Зберегти зміни', + 'submitting' => 'Збереження...', + ], + + 'delete' => [ + 'title' => 'Видалити мітку', + 'description' => 'Ви впевнені, що хочете видалити цю мітку? Цю дію не можна скасувати.', + 'confirm' => 'Видалити', + 'cancel' => 'Скасувати', + ], + + 'flash' => [ + 'created' => 'Мітку успішно створено!', + 'updated' => 'Мітку успішно оновлено!', + 'deleted' => 'Мітку успішно видалено!', + ], +]; diff --git a/lang/uk/mail.php b/lang/uk/mail.php new file mode 100644 index 000000000..e81e5b741 --- /dev/null +++ b/lang/uk/mail.php @@ -0,0 +1,22 @@ + [ + 'subject' => ':name згадав(ла) вас у TryPost', + 'title' => ':name згадав(ла) вас', + 'intro' => ':name згадав(ла) вас у коментарі до поста.', + 'cta' => 'Переглянути коментар', + ], + + 'workspace_connections_disconnected' => [ + 'subject' => '{1} :count акаунт потрібно перепідключити в :workspace|[2,*] :count акаунтів потрібно перепідключити в :workspace', + 'title' => 'Акаунти потребують перепідключення', + 'intro' => 'Наступні соціальні акаунти у вашому робочому просторі :workspace було від’єднано, і їх потрібно перепідключити:', + 'reasons_title' => 'Це могло статися через:', + 'reason_expired' => 'Термін дії токенів доступу минув', + 'reason_revoked' => 'Ви відкликали доступ TryPost на платформі', + 'reason_changed' => 'Платформа змінила вимоги до автентифікації', + 'reconnect_cta' => 'Перепідключіть ці акаунти, щоб продовжити планування та публікацію постів.', + 'button' => 'Перепідключити акаунти', + ], +]; diff --git a/lang/uk/notifications.php b/lang/uk/notifications.php new file mode 100644 index 000000000..0987d4518 --- /dev/null +++ b/lang/uk/notifications.php @@ -0,0 +1,18 @@ + [ + 'title' => 'Ваш пост готовий', + 'body' => 'AI щойно завершив роботу. Натисніть, щоб переглянути та опублікувати.', + ], + 'account_disconnected' => [ + 'title' => 'Акаунт :platform від’єднано', + 'body' => ':account потрібно перепідключити', + ], + 'account_token_expired' => [ + 'title' => 'Акаунт :platform потрібно перепідключити', + 'body' => 'Сесію :account завершено — перепідключіть, щоб продовжити публікацію', + ], +]; diff --git a/lang/uk/onboarding.php b/lang/uk/onboarding.php new file mode 100644 index 000000000..6c8663b1d --- /dev/null +++ b/lang/uk/onboarding.php @@ -0,0 +1,57 @@ + 'Ласкаво просимо до TryPost', + 'description' => 'Розкажіть, що найкраще описує вас або ваш бізнес, щоб ми могли адаптувати ваш досвід.', + 'continue' => 'Продовжити', + 'personas' => [ + 'creator' => 'Автор контенту', + 'freelancer' => 'Фрілансер', + 'developer' => 'Розробник', + 'startup' => 'Стартап', + 'agency' => 'Агенція', + 'small_business' => 'Малий бізнес', + 'marketer' => 'Маркетолог', + 'online_store' => 'Інтернет-магазин', + 'other' => 'Інше', + ], + 'goals_title' => 'Яка ваша мета з TryPost?', + 'goals_description' => 'Виберіть усе, що підходить, і ми налаштуємо TryPost для вас.', + 'goals' => [ + 'save_time' => 'Економити час, публікуючи всюди одразу', + 'ai_content' => 'Створювати пости швидше з AI', + 'plan_calendar' => 'Планувати пости в календарі', + 'stay_on_brand' => 'Тримати кожен пост у стилі бренду', + 'grow_audience' => 'Збільшувати аудиторію та залучення', + 'drive_sales' => 'Отримувати більше трафіку та продажів', + 'manage_clients' => 'Керувати кількома брендами або клієнтами', + 'team_collaboration' => 'Працювати з командою', + 'automate_api' => 'Автоматизувати публікацію через API, MCP або код', + 'track_performance' => 'Бачити, як працюють мої пости', + 'just_exploring' => 'Поки що просто досліджую', + 'other' => 'Щось інше', + ], + 'referral_source_title' => 'Як ви нас знайшли?', + 'referral_source_description' => 'Це допомагає нам зрозуміти, як люди дізнаються про TryPost.', + 'referral_source' => [ + 'google' => 'Google або пошук', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram або Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI-асистент (ChatGPT, Claude…)', + 'friend' => 'Друг або колега', + 'blog' => 'Блог, розсилка або стаття', + 'other' => 'Щось інше', + ], + 'connect' => [ + 'title' => 'Підключіть першу мережу', + 'description' => 'Під’єднайте хоча б один соціальний акаунт, щоб почати планування. Ви зможете додати більше будь-коли.', + 'must_connect' => 'Підключіть хоча б одну мережу, щоб продовжити.', + ], +]; diff --git a/lang/uk/pagination.php b/lang/uk/pagination.php new file mode 100644 index 000000000..54e5a82b5 --- /dev/null +++ b/lang/uk/pagination.php @@ -0,0 +1,19 @@ + '« Попередня', + 'next' => 'Наступна »', + +]; diff --git a/lang/uk/passwords.php b/lang/uk/passwords.php new file mode 100644 index 000000000..ed9b73940 --- /dev/null +++ b/lang/uk/passwords.php @@ -0,0 +1,22 @@ + 'Ваш пароль було скинуто.', + 'sent' => 'Ми надіслали посилання для скидання пароля на вашу електронну пошту.', + 'throttled' => 'Зачекайте, перш ніж повторити спробу.', + 'token' => 'Цей токен скидання пароля недійсний.', + 'user' => 'Ми не знайшли користувача з такою адресою електронної пошти.', + +]; diff --git a/lang/uk/posts.php b/lang/uk/posts.php new file mode 100644 index 000000000..50a859103 --- /dev/null +++ b/lang/uk/posts.php @@ -0,0 +1,690 @@ + 'Пости', + 'search' => 'Пошук постів...', + 'all_posts' => 'Усі пости', + 'new_post' => 'Новий пост', + 'no_posts' => 'Постів не знайдено', + 'no_search_results' => 'Жоден пост не відповідає вашому пошуку', + 'try_different_search' => 'Спробуйте інше ключове слово або очистіть пошук.', + 'start_creating' => 'Почніть із створення свого першого поста.', + 'filter_by_label' => 'Фільтр за міткою', + 'label_search_placeholder' => 'Пошук міток...', + 'no_labels' => 'Міток не знайдено.', + 'clear_label_filter' => 'Очистити фільтр міток', + 'table' => [ + 'post' => 'Пост', + 'status' => 'Статус', + 'content' => 'Вміст', + 'platforms' => 'Платформи', + 'labels' => 'Мітки', + 'scheduled_at' => 'Дата', + 'actions' => '', + ], + 'manage_posts' => 'Керуйте всіма своїми постами', + 'delete_confirm' => 'Ви впевнені, що хочете видалити цей пост?', + 'by' => 'від', + + 'actions' => [ + 'view' => 'Переглянути пост', + 'delete' => 'Видалити', + 'duplicate' => 'Дублювати', + 'copy_id' => 'Копіювати ID', + 'copied' => 'ID скопійовано в буфер обміну', + ], + + 'form' => [ + 'post_type' => 'Тип поста', + 'board' => 'дошка', + 'select_board' => 'Виберіть дошку', + 'search_board' => 'Пошук дошки...', + 'no_board_found' => 'дошку не знайдено', + 'media' => 'Медіа', + 'min' => 'Мін.', + 'uploading' => 'Завантаження...', + 'drop_to_upload' => 'Відпустіть, щоб завантажити', + 'drag_and_drop' => 'Перетягніть або натисніть, щоб завантажити', + 'photos_and_videos' => 'Фото та відео', + 'photos_only' => 'Лише фото', + 'videos_only' => 'Лише відео', + 'drag_to_reorder' => 'Перетягніть, щоб змінити порядок', + 'caption' => 'Підпис', + 'write_caption' => 'Напишіть підпис...', + 'content_exceeds_platform' => ':platform: перевищення на :over симв. (макс. :limit).', + 'tiktok' => [ + 'settings' => 'Налаштування TikTok', + 'variant_label' => 'Тип поста', + 'variant' => [ + 'video' => 'Відео', + 'photo' => 'Фотокарусель', + ], + 'posting_to' => 'Публікація в', + 'privacy_level' => 'Хто може бачити це відео?', + 'privacy_placeholder' => 'Виберіть, хто може бачити цей пост', + 'privacy' => [ + 'public' => 'Доступно всім', + 'friends' => 'Взаємні друзі-підписники', + 'followers' => 'Підписники', + 'private' => 'Лише я', + 'private_disabled_branded' => 'Видимість брендованого контенту не можна встановити як «Лише я».', + ], + 'privacy_hint' => 'Доступні варіанти залежать від налаштувань вашого акаунта TikTok.', + 'auto_add_music' => 'Автоматично додати музику', + 'auto_add_music_hint' => 'Ця функція доступна лише для фото. Буде додано музику за замовчуванням, яку можна змінити пізніше.', + 'yes' => 'Так', + 'no' => 'Ні', + 'allow_users' => 'Дозволити користувачам:', + 'comments' => 'Коментувати', + 'duet' => 'Duet', + 'stitch' => 'Stitch', + 'is_aigc' => 'Відео створено за допомогою AI', + 'disclose' => 'Розкрити вміст відео', + 'disclose_hint' => 'Увімкніть, щоб указати, що це відео просуває товари чи послуги в обмін на щось цінне. Ваше відео може просувати вас, третю сторону або обох.', + 'promotional_organic_title' => 'Ваше фото/відео буде позначено як «Рекламний контент».', + 'promotional_paid_title' => 'Ваше фото/відео буде позначено як «Платне партнерство».', + 'promotional_description' => 'Це не можна змінити після публікації відео.', + 'compliance_incomplete' => 'Потрібно вказати, чи ваш контент просуває вас, третю сторону чи обох.', + 'privacy_required' => 'Під час публікації потрібно вказати рівень приватності TikTok.', + 'branded_cleared_private' => 'Налаштування приватності скинуто, оскільки брендований контент не може бути приватним.', + 'interaction_disabled_by_creator' => 'Вимкнено в налаштуваннях вашого акаунта TikTok', + 'max_duration_exceeded' => 'Відео триває :duration с, але цей акаунт може публікувати відео тривалістю не більше :max с.', + 'processing_hint' => 'Після публікації може знадобитися кілька хвилин, щоб контент обробився та з’явився у вашому профілі TikTok.', + 'brand_organic' => 'Ваш бренд', + 'brand_organic_hint' => 'Ви просуваєте себе або власний бренд. Це відео буде класифіковано як Brand Organic.', + 'brand_content' => 'Брендований контент', + 'brand_content_hint' => 'Ви просуваєте інший бренд або третю сторону. Це відео буде класифіковано як Branded Content.', + 'compliance' => [ + 'agree' => 'Публікуючи, ви погоджуєтеся з', + 'music_usage' => 'Підтвердженням використання музики TikTok', + 'and' => 'та', + 'branded_policy' => 'Політикою брендованого контенту', + ], + ], + 'instagram' => [ + 'settings' => 'Налаштування Instagram', + 'posting_to' => 'Публікація в', + 'variant_label' => 'Тип поста', + 'variant' => [ + 'feed' => 'Пост у стрічці', + 'reel' => 'Reels', + 'story' => 'Stories', + ], + 'aspect_label' => 'Співвідношення сторін', + 'aspect' => [ + 'square' => 'Квадрат (1:1)', + 'portrait' => 'Портрет (4:5)', + 'landscape' => 'Альбомна (16:9)', + 'original' => 'Оригінал', + ], + ], + 'facebook' => [ + 'settings' => 'Налаштування Facebook', + 'posting_to' => 'Публікація в', + 'variant_label' => 'Тип поста', + 'variant' => [ + 'post' => 'Пост', + 'reel' => 'Reels', + 'story' => 'Stories', + ], + 'aspect_label' => 'Співвідношення сторін', + 'aspect' => [ + 'square' => 'Квадрат (1:1)', + 'portrait' => 'Портрет (4:5)', + 'landscape' => 'Альбомна (16:9)', + 'original' => 'Оригінал', + ], + ], + 'linkedin' => [ + 'settings' => 'Налаштування LinkedIn', + 'settings_page' => 'Налаштування сторінки LinkedIn', + 'posting_to' => 'Публікація в', + 'document_title' => 'Назва документа', + 'document_title_placeholder' => 'Відображається у вашому пості з PDF-документом', + ], + 'pinterest' => [ + 'settings' => 'Налаштування Pinterest', + 'posting_to' => 'Публікація в', + 'variant_label' => 'Тип піна', + 'variant' => [ + 'pin' => 'Pin', + 'video_pin' => 'Відео-пін', + 'carousel' => 'Карусель', + ], + 'board' => 'дошка', + 'select_board' => 'Виберіть дошку', + 'no_boards' => 'дошок Pinterest не знайдено. Спочатку створіть дошку у своєму акаунті Pinterest.', + 'boards_truncated' => 'Деякі дошки не вдалося завантажити. Якщо вашої немає, відкрийте Pinterest і спробуйте знову.', + 'search_board' => 'Пошук досок...', + 'no_board_found' => 'Жодна дошка не відповідає вашому пошуку.', + 'board_required' => 'Виберіть дошку Pinterest, щоб опублікувати цей пост.', + 'title' => 'Заголовок', + 'title_placeholder' => 'Необовʼязковий заголовок піна', + 'link' => 'Посилання призначення', + 'link_placeholder' => 'https://example.com', + 'link_invalid' => 'Вкажіть коректне посилання призначення (http або https).', + 'title_max' => 'Заголовок не може бути довшим за 100 символів.', + 'link_max' => 'Посилання призначення не може бути довшим за 2048 символів.', + ], + 'discord' => [ + 'settings' => 'Налаштування Discord', + 'posting_to' => 'Публікація в', + 'channel' => 'Канал', + 'select_channel' => 'Виберіть канал', + 'loading_channels' => 'Завантаження каналів…', + 'search_channel' => 'Пошук каналів…', + 'no_channels' => 'Каналів не знайдено.', + 'channel_required' => 'Виберіть канал Discord, щоб опублікувати цей пост.', + 'mentions' => 'Згадки', + 'search_mention' => 'Згадайте роль або учасника…', + 'embeds' => 'Вбудовування', + 'embed' => 'Вбудовування', + 'add_embed' => 'Додати вбудовування', + 'embed_title' => 'Заголовок вбудовування', + 'embed_description' => 'Опис вбудовування', + 'embed_url' => 'URL вбудовування', + 'embed_image' => 'URL зображення', + 'embed_color' => 'Колір', + ], + 'warnings' => [ + 'no_variant' => 'Виберіть тип поста, щоб продовжити.', + 'requires_media' => 'Цей тип поста потребує принаймні одного зображення або відео.', + 'max_files_exceeded' => 'Цей тип поста приймає не більше :max медіафайлів (у вас :current).', + 'min_files_required' => 'Цей тип поста потребує принаймні :min медіафайлів (у вас :current).', + 'no_video_allowed' => 'Цей тип поста не приймає відео.', + 'no_image_allowed' => 'Цей тип поста приймає лише відео.', + 'no_document_allowed' => 'Цей тип поста не приймає PDF-документи.', + 'no_mixed_media' => 'Зображення та відео не можна поєднувати в одному пості.', + 'document_not_alone' => 'PDF потрібно публікувати окремо, без інших зображень або відео.', + 'gif_not_allowed' => 'Ця платформа не приймає GIF. Видаліть GIF або виберіть іншу мережу.', + 'image_too_large' => 'Зображення перевищує ліміт :max для цього типу поста (у вас :current).', + 'video_too_large' => 'Відео перевищує ліміт :max для цього типу поста (у вас :current).', + 'document_too_large' => 'PDF перевищує ліміт :max для цього типу поста (у вас :current).', + 'video_too_long' => 'Відео триває :current, але цей тип поста дозволяє не більше :max.', + 'aspect_ratio_too_narrow' => 'Співвідношення сторін :current занадто вузьке для цього типу поста (мін. :min).', + 'aspect_ratio_too_wide' => 'Співвідношення сторін :current занадто широке для цього типу поста (макс. :max).', + ], + ], + + 'status' => [ + 'pending' => 'Очікує', + 'draft' => 'Чернетка', + 'scheduled' => 'Заплановано', + 'publishing' => 'Публікується', + 'retrying' => 'Повторна спроба', + 'published' => 'Опубліковано', + 'partially_published' => 'Частково опубліковано', + 'failed' => 'Помилка', + ], + + 'descriptions' => [ + 'draft' => 'Пости, що очікують на планування', + 'scheduled' => 'Пости, заплановані до публікації', + 'published' => 'Уже опубліковані пости', + ], + + 'ai' => [ + 'generate' => [ + 'button_tooltip' => 'Згенерувати за допомогою AI', + 'title' => 'Згенерувати пост за допомогою AI', + 'description' => 'Опишіть, про що має бути пост. AI використає контекст вашого бренду, щоб написати його.', + 'prompt_label' => 'Про що цей пост?', + 'prompt_placeholder' => 'наприклад, Оголосити нашу нову функцію генерації зображень для каруселей', + 'preview_label' => 'Попередній перегляд', + 'start' => 'Згенерувати', + 'apply' => 'Використати цей вміст', + 'retry' => 'Спробувати знову', + 'cancel' => 'Скасувати', + ], + 'review' => [ + 'button_tooltip' => 'Перевірити за допомогою AI', + 'title' => 'Перевірити пост за допомогою AI', + 'description' => 'AI перевіряє граматику, орфографію та зрозумілість. Застосовуйте пропозиції, з якими ви погоджуєтеся.', + 'loading' => 'Перевіряємо ваш текст...', + 'no_issues' => 'Проблем не знайдено. Виглядає добре.', + 'original' => 'Оригінал', + 'suggestion' => 'Пропозиція', + 'apply' => 'Застосувати', + 'apply_all' => 'Застосувати все', + 'applied' => 'Застосовано', + 'cancel' => 'Скасувати', + ], + 'image_regenerate' => [ + 'button' => 'Скоригувати', + 'title' => 'Скоригувати AI-зображення', + 'description' => 'Опишіть правку. Нове зображення замінить поточне та збереже своє місце в каруселі.', + 'instruction_label' => 'Інструкція', + 'instruction_placeholder' => 'наприклад, Виправити друкарську помилку в заголовку та зробити фон світлішим.', + 'processing' => 'Перегенеровуємо зображення... це може зайняти кілька секунд.', + 'submit' => 'Перегенерувати зображення', + 'cancel' => 'Скасувати', + 'success' => 'Зображення оновлено. Нова версія замінила попередню у вашому пості.', + 'fallback_title' => 'Покращити текст на цьому зображенні', + 'errors' => [ + 'required' => 'Інструкція обов’язкова.', + 'start_failed' => 'Не вдалося розпочати перегенерацію.', + 'unavailable' => 'Зараз неможливо перегенерувати це зображення.', + 'timeout' => 'Перегенерація триває довше, ніж очікувалося. Спробуйте через мить.', + 'media_not_found' => 'Медіафайл не знайдено.', + 'not_ai_media' => 'Перегенеровувати можна лише медіа, згенеровані AI.', + ], + ], + 'templates' => [ + 'image_card' => [ + 'name' => 'Пост із зображенням', + 'description' => 'AI-зображення із заголовком та підписом.', + ], + 'carousel' => [ + 'name' => 'Карусель', + 'description' => 'Карусель із кількох слайдів із підписом.', + ], + 'tweet_card' => [ + 'name' => 'Картка твіта', + 'description' => 'Ваш пост у стилі публікації X/Twitter.', + ], + 'tweet_card_image' => [ + 'name' => 'Картка твіта з фото', + 'description' => 'Ваш пост у вигляді картки X/Twitter на розмитому фото.', + ], + ], + ], + + 'show' => [ + 'title' => 'Деталі поста', + 'edit' => 'Редагувати', + 'back' => 'Назад', + 'no_content' => 'Без підпису', + 'platforms' => 'Платформи', + 'no_platforms' => 'Платформи не вибрано.', + 'view_on_platform' => 'Переглянути на платформі', + 'published_on' => 'Опубліковано :date', + 'scheduled_for' => 'Заплановано на :date', + 'draft' => 'Чернетка', + 'status_pending' => 'Очікує', + 'metrics' => 'Метрики', + 'metrics_loading' => 'Завантаження метрик…', + 'metrics_unavailable' => 'Метрики для цієї платформи поки недоступні.', + 'metrics_empty' => 'Метрики відсутні.', + ], + + 'edit' => [ + 'title' => 'Редагувати пост', + 'view_title' => 'Переглянути пост', + 'labels' => 'Мітки', + 'no_labels' => 'Мітки ще не створено', + 'schedule' => 'Запланувати', + 'pick_time' => 'Виберіть час', + 'pick_time_past' => 'Виберіть дату та час у майбутньому.', + 'post_now' => 'Опублікувати зараз', + 'time' => 'Час', + 'cancel' => 'Скасувати', + 'delete' => 'Видалити', + 'schedule_for' => 'Запланувати на', + 'schedule_date' => 'Дата планування', + 'unschedule' => 'Скасувати планування', + 'saving' => 'Збереження...', + 'saved' => 'Збережено', + 'draft' => 'Чернетка', + 'media' => 'Медіа', + 'add_media' => 'Додати медіа', + 'caption' => 'Підпис', + 'caption_placeholder' => 'Напишіть підпис...', + 'compose_title' => 'Створити пост', + 'compose_subtitle' => 'Складіть повідомлення та додайте медіа', + 'preview_empty' => [ + 'title' => 'Платформу не вибрано', + 'description' => 'Виберіть платформу для публікації, щоб побачити попередній перегляд.', + ], + 'drop_zone_title' => 'Додати медіа', + 'drop_zone_subtitle' => 'Перетягніть файли або натисніть, щоб обрати', + 'add' => 'Додати', + 'publish_to' => 'Опублікувати в', + 'organize' => 'Організувати', + 'signatures' => 'Підписи', + 'view_on_platform' => 'Переглянути на платформі', + 'platform_status' => 'Статус платформи', + 'compliance_incomplete' => 'Деякі налаштування платформи не заповнені або несумісні з прикріпленим медіа.', + 'compliance' => [ + 'requires_content_or_media' => 'Додайте текст або медіа для публікації.', + 'requires_media' => 'Додайте зображення або відео для публікації тут.', + 'too_many_files' => 'Для цього формату допускається не більше :max файлів.', + 'too_few_files' => 'Додайте принаймні :min файлів для цього формату.', + 'no_videos' => 'Для цього формату допустимі лише зображення.', + 'no_images' => 'Для цього формату допустимі лише відео.', + 'no_mixed_media' => 'Зображення та відео не можна поєднувати в одному пості.', + 'no_gifs' => 'GIF тут не підтримуються.', + 'no_documents' => 'PDF-документи не підтримуються цим форматом.', + 'document_not_alone' => 'PDF потрібно публікувати окремо, без інших зображень або відео.', + 'video_too_large' => 'Відео перевищує ліміт розміру для цієї платформи.', + 'video_too_long' => 'Для цього формату відео має бути коротшим за :seconds секунд.', + 'image_too_large' => 'Зображення перевищує ліміт розміру для цієї платформи.', + 'document_too_large' => 'PDF перевищує ліміт розміру для цієї платформи.', + 'aspect_ratio_invalid' => 'Співвідношення сторін не підтримується цим форматом.', + 'no_content_type' => 'Виберіть тип контенту для цієї платформи.', + 'requires_text' => 'Додайте текст — для цього формату потрібен заголовок.', + ], + 'publishing' => 'Публікується...', + 'publishing_overlay_title' => 'Ваш пост публікується', + 'publishing_overlay_subtitle' => 'Це може зайняти кілька миттєвостей. Ви можете спокійно покинути цю сторінку.', + 'scheduled_overlay_title' => 'Цей пост заплановано', + 'scheduled_overlay_subtitle' => 'Заплановано на :date. Спочатку скасуйте планування, щоб внести зміни.', + 'unschedule_cta' => 'Скасувати планування для редагування', + + 'tabs' => [ + 'compose' => 'Написати', + 'preview' => 'Попередній перегляд', + 'channels' => 'Канали', + 'comments' => 'Коментарі', + 'comments_empty' => 'Поки що немає коментарів.', + ], + + 'media_picker' => [ + 'title' => 'Вибрати з галереї', + 'search' => 'Пошук медіа...', + 'empty' => 'У галереї поки немає медіа', + 'cancel' => 'Скасувати', + 'add' => 'Додати', + 'add_count' => 'Додати :count', + ], + + 'alt_text' => [ + 'label' => 'Альтернативний текст', + 'edit' => 'Редагувати альтернативний текст', + 'placeholder' => 'Опишіть це зображення для людей, які використовують програми зчитування з екрана', + 'hint' => 'Публікується на платформах, що підтримують описи зображень.', + 'save' => 'Зберегти', + ], + + 'emoji_picker' => [ + 'search' => 'Пошук емодзі', + 'empty' => 'Емодзі не знайдено', + 'recent' => 'Часто використовувані', + 'smileys' => 'Смайлики та емоції', + 'people' => 'Люди та тіло', + 'nature' => 'Тварини та природа', + 'food' => 'Їжа та напої', + 'activities' => 'Активності', + 'travel' => 'Подорожі та місця', + 'objects' => 'Об’єкти', + 'symbols' => 'Символи', + 'flags' => 'Прапори', + ], + + 'status' => [ + 'pending' => 'Очікує', + 'scheduled' => 'Заплановано', + 'published' => 'Опубліковано', + 'publishing' => 'Публікується...', + 'retrying' => 'Повторна спроба...', + 'failed' => 'Помилка', + ], + + 'delete_modal' => [ + 'title' => 'Видалити пост', + 'description' => 'Ви впевнені, що хочете видалити цей пост? Цю дію не можна скасувати.', + 'action' => 'Видалити', + 'cancel' => 'Скасувати', + ], + + 'sync_enable' => [ + 'title' => 'Увімкнути синхронізацію?', + 'description' => 'Усі платформи використовуватимуть однаковий вміст. Будь-які індивідуальні правки для окремих платформ буде замінено поточним вмістом.', + 'cancel' => 'Скасувати', + 'action' => 'Увімкнути синхронізацію', + ], + + 'sync_disable' => [ + 'title' => 'Вимкнути синхронізацію?', + 'description' => 'Кожна платформа збереже свій поточний вміст, але майбутні правки застосовуватимуться лише до платформи, яку ви редагуєте.', + 'customize_note' => 'Ви зможете налаштовувати вміст для кожної платформи окремо.', + 'cancel' => 'Скасувати', + 'action' => 'Вимкнути синхронізацію', + ], + + 'platforms_dialog' => [ + 'title' => 'Виберіть платформи', + 'description' => 'Виберіть, на які платформи опублікувати цей пост.', + ], + + 'signatures_modal' => [ + 'search' => 'Пошук підписів...', + 'no_results' => 'Підписів не знайдено.', + ], + + 'validation' => [ + 'select_board' => 'Виберіть дошку', + 'images_not_supported' => 'Зображення не підтримуються', + 'videos_not_supported' => 'Відео не підтримуються', + 'max_images' => 'Не більше :count зображень', + 'requires_media' => 'Потрібне медіа', + 'requires_content' => 'Потрібен текстовий вміст', + 'exceeded' => 'перевищено на :count', + 'does_not_support_images' => ':platform не підтримує зображення', + 'supports_up_to_images' => ':platform підтримує до :count зображень', + 'does_not_support_videos' => ':platform не підтримує відео', + ], + ], + + 'content_types' => [ + 'instagram_feed' => [ + 'label' => 'Пост у стрічці', + 'description' => 'З’являється у вашій стрічці та профілі', + ], + 'instagram_reel' => [ + 'label' => 'Reels', + 'description' => 'Коротке відео до 15 хвилин', + ], + 'instagram_story' => [ + 'label' => 'Stories', + 'description' => 'Зникає через 24 години', + ], + 'linkedin_post' => [ + 'label' => 'Пост', + 'description' => 'Звичайний пост — одне зображення, кілька зображень, відео або PDF', + ], + 'linkedin_page_post' => [ + 'label' => 'Пост', + 'description' => 'Звичайний пост — одне зображення, кілька зображень, відео або PDF', + ], + 'facebook_post' => [ + 'label' => 'Пост', + 'description' => 'Звичайний пост на вашій сторінці', + ], + 'facebook_reel' => [ + 'label' => 'Reels', + 'description' => 'Коротке відео до 90 секунд', + ], + 'facebook_story' => [ + 'label' => 'Stories', + 'description' => 'Вертикальна відеоісторія, до 60 секунд', + ], + 'tiktok_video' => [ + 'label' => 'Відео', + 'description' => 'Коротке відео', + ], + 'tiktok_photo' => [ + 'label' => 'Фотокарусель', + 'description' => 'До 35 фото у вигляді каруселі', + ], + 'youtube_short' => [ + 'label' => 'Short', + 'description' => 'Вертикальне відео до 3 хвилин', + ], + 'x_post' => [ + 'label' => 'Пост', + 'description' => 'Твіт із текстом та медіа', + ], + 'threads_post' => [ + 'label' => 'Пост', + 'description' => 'Текстовий пост із необов’язковим медіа', + ], + 'pinterest_pin' => [ + 'label' => 'Pin', + 'description' => 'Звичайний пін із зображенням', + ], + 'pinterest_video_pin' => [ + 'label' => 'Відео-пін', + 'description' => 'Відео-пін (4 с – 15 хв)', + ], + 'pinterest_carousel' => [ + 'label' => 'Карусель', + 'description' => 'Карусель із кількох зображень (2–5 зображень)', + ], + 'bluesky_post' => [ + 'label' => 'Пост', + 'description' => 'Текстовий пост із необов’язковими зображеннями', + ], + 'mastodon_post' => [ + 'label' => 'Пост', + 'description' => 'Текстовий пост із необов’язковим медіа', + ], + 'telegram_post' => [ + 'label' => 'Пост', + 'description' => 'Текстовий пост із необов’язковим медіа', + ], + 'discord_message' => [ + 'label' => 'Повідомлення', + 'description' => 'Повідомлення в канал Discord із необов’язковим медіа та вбудовуваннями', + ], + ], + + 'platforms' => [ + 'linkedin' => 'LinkedIn', + 'linkedin-page' => 'Сторінка LinkedIn', + 'x' => 'X', + 'tiktok' => 'TikTok', + 'youtube' => 'YouTube Shorts', + 'facebook' => 'Сторінка Facebook', + 'instagram' => 'Instagram', + 'threads' => 'Threads', + 'pinterest' => 'Pinterest', + 'bluesky' => 'Bluesky', + 'mastodon' => 'Mastodon', + ], + + 'flash' => [ + 'scheduled' => 'Пост успішно заплановано!', + 'deleted' => 'Пост успішно видалено!', + 'duplicated' => 'Пост продубльовано як чернетку.', + 'cannot_edit_finalized' => 'Цей пост уже оброблено і не можна опублікувати повторно. Продублюйте його, щоб спробувати знову.', + 'cannot_delete_published' => 'Опубліковані пости не можна видалити.', + 'connect_first' => 'Підключіть принаймні одну соціальну мережу перед створенням поста.', + ], + + 'errors' => [ + 'account_disconnected' => 'Соціальний акаунт відключено', + 'account_inactive' => 'Соціальний акаунт деактивовано', + 'account_token_expired' => 'Сесія соціального акаунта закінчилася — перепідключіть', + ], + + 'delete' => [ + 'title' => 'Видалити пост?', + 'description' => 'Цю дію не можна скасувати. Пост і все його медіа буде безповоротно видалено.', + 'confirm' => 'Так, видалити', + 'cancel' => 'Скасувати', + ], + + 'create' => [ + 'title' => 'Створити новий пост', + 'description' => 'Виберіть, з чого хочете почати.', + 'scratch_title' => 'Почати з нуля', + 'scratch_description' => 'Відкрийте порожній пост і напишіть усе самі.', + 'ai_title' => 'Згенерувати за допомогою AI', + 'ai_description' => 'Опишіть, що хочете, і AI згенерує вміст за вас.', + 'ai_configure_description' => 'Виберіть формат і опишіть пост, який хочете створити.', + 'ai_pick_template_description' => 'Виберіть стиль для вашого AI-поста.', + 'template_title' => 'Використати шаблон', + 'template_description' => 'Виберіть із наших готових шаблонів і налаштуйте.', + 'coming_soon' => 'Незабаром', + + 'preview' => [ + 'image_title' => 'Заголовок зображення', + 'image_body' => 'Текст зображення', + ], + + 'steps' => [ + 'template_picker_title' => 'Виберіть стиль', + 'format_title' => 'Виберіть формат', + 'format_description' => 'Виберіть тип поста, який хочете створити.', + 'account_title' => 'Виберіть акаунт', + 'account_description' => 'Виберіть соціальний акаунт для публікації.', + 'media_title' => 'Параметри медіа', + 'media_carousel' => 'Скільки слайдів?', + 'media_optional' => 'Додати зображення?', + 'media_optional_label' => 'Скільки зображень?', + 'media_none' => 'Немає', + 'media_count_label' => 'Кількість зображень', + 'brand_colors_label' => 'Кольори бренду', + 'brand_colors_description' => 'Застосовуйте фірмову палітру вашого workspace до згенерованих зображень. Вимкніть, щоб AI підібрав кольори під вміст.', + 'prompt_title' => 'Опишіть свій пост', + 'prompt_label' => 'Про що цей пост?', + 'prompt_placeholder' => 'наприклад, Оголосити нашу нову функцію каруселі для Instagram', + 'preview_error' => 'Щось пішло не так. Спробуйте ще раз.', + 'loading_page_title' => 'Генеруємо ваш пост', + 'loading_eta' => 'Орієнтовний час: близько :minutes.', + 'loading_eta_minute_one' => '1 хвилина', + 'loading_eta_minute_other' => ':count хвилин', + 'loading_leave_title' => 'Ви можете продовжувати працювати.', + 'loading_leave_body' => 'Ми повідомимо вас, коли пост буде готовий.', + 'loading_leave_cta' => 'Перейти до календаря', + 'loading_create_another_cta' => 'Створити ще один пост', + 'loading_tip_credits' => 'Кожне AI-зображення використовує близько 15 кредитів.', + 'loading_tip_edit' => 'Ви зможете відредагувати все, коли пост буде готовий.', + 'loading_tip_draft' => 'Згенеровані пости потрапляють у чернетки.', + 'loading_tip_brand' => 'Налаштуйте параметри бренду, щоб впливати на майбутні пости.', + 'loading_tip_carousel' => 'У каруселі створюється один слайд на кожне завантажене зображення.', + 'loading_tip_quality' => 'Якість зображень налаштована на баланс швидкості та вартості.', + 'create' => 'Створити пост', + 'back' => 'Назад', + 'next' => 'Продовжити', + 'cancel' => 'Скасувати', + 'discard' => 'Скасувати', + 'retry' => 'Спробувати знову', + 'no_platforms' => 'Немає підключених акаунтів', + 'connect_first' => 'Підключіть принаймні один соціальний акаунт, щоб використовувати генерацію AI.', + 'no_account_for_template' => 'Підключіть сумісний акаунт, щоб використати цей шаблон.', + + 'format' => [ + 'instagram_feed' => 'Пост у стрічці Instagram', + 'instagram_carousel' => 'Карусель Instagram', + 'linkedin_post' => 'Пост LinkedIn', + 'linkedin_page_post' => 'Пост сторінки LinkedIn', + 'x_post' => 'Пост X', + 'bluesky_post' => 'Пост Bluesky', + 'threads_post' => 'Пост Threads', + 'mastodon_post' => 'Пост Mastodon', + 'telegram_post' => 'Пост Telegram', + 'discord_message' => 'Повідомлення Discord', + 'facebook_post' => 'Пост Facebook', + 'pinterest_pin' => 'Pin Pinterest', + 'instagram_story' => 'Stories Instagram', + 'facebook_story' => 'Stories Facebook', + ], + ], + ], + + 'templates' => [ + 'browser_title' => 'Виберіть шаблон', + 'browser_description' => 'Почніть із готового шаблону та адаптуйте його.', + 'search_placeholder' => 'Пошук шаблонів…', + 'no_search_results' => 'Жоден шаблон не відповідає вашому пошуку', + 'try_different_search' => 'Спробуйте інше ключове слово або очистіть пошук.', + 'slides_count' => '{count} слайд|{count} слайди|{count} слайдів', + 'all_platforms' => 'Усі платформи', + 'platform_search_placeholder' => 'Пошук платформи…', + 'no_platform_match' => 'Жодна платформа не відповідає.', + 'use_this' => 'Використати цей шаблон', + 'no_templates' => 'Немає доступних шаблонів.', + 'applying' => 'Застосовуємо шаблон…', + 'category' => [ + 'product_launch' => 'Запуск продукту', + 'promotion' => 'Акція', + 'educational' => 'Навчальне', + 'behind_the_scenes' => 'За кулісами', + 'testimonial' => 'Відгук', + 'industry_tip' => 'Галузева порада', + 'event' => 'Подія', + 'engagement' => 'Залучення', + ], + ], +]; diff --git a/lang/uk/settings.php b/lang/uk/settings.php new file mode 100644 index 000000000..c191c37da --- /dev/null +++ b/lang/uk/settings.php @@ -0,0 +1,384 @@ + 'Налаштування', + 'description' => 'Керуйте профілем та налаштуваннями облікового запису', + + 'hub' => [ + 'title' => 'Налаштування', + 'description' => 'Виберіть, чим хочете керувати.', + 'profile' => [ + 'title' => 'Профіль', + 'description' => 'Оновіть особисті дані, пароль і налаштування сповіщень.', + ], + 'workspace' => [ + 'title' => 'Робочий простір', + 'description' => 'Налаштуйте робочий простір, бренд, учасників і API-ключі.', + ], + 'account' => [ + 'title' => 'Обліковий запис', + 'description' => 'Керуйте даними облікового запису, використанням і оплатою.', + ], + ], + + 'nav' => [ + 'profile' => 'Профіль', + 'authentication' => 'Автентифікація', + 'workspace' => 'Робочий простір', + 'members' => 'Учасники', + 'notifications' => 'Сповіщення', + 'billing' => 'Оплата', + ], + + 'notifications' => [ + 'title' => 'Налаштування сповіщень', + 'heading' => 'Email-сповіщення', + 'description' => 'Виберіть, які email-сповіщення ви хочете отримувати', + 'post_published' => 'Пост опубліковано', + 'post_published_description' => 'Отримуйте email, коли ваш пост успішно опубліковано', + 'post_failed' => 'Помилка публікації', + 'post_failed_description' => 'Отримуйте email, коли публікація поста не вдалася', + 'account_disconnected' => 'Акаунт від’єднано', + 'account_disconnected_description' => 'Отримуйте email, коли соціальний акаунт від’єднано', + 'save' => 'Зберегти налаштування', + ], + + 'profile' => [ + 'title' => 'Налаштування профілю', + 'photo_heading' => 'Фото профілю', + 'photo_description' => 'Завантажте фото профілю', + 'heading' => 'Інформація профілю', + 'description' => 'Оновіть ім’я та адресу email', + 'avatar' => 'Аватар', + 'name' => 'Ім’я', + 'name_placeholder' => 'Повне ім’я', + 'email' => 'Адреса email', + 'email_placeholder' => 'Адреса email', + 'email_unverified' => 'Вашу адресу email не підтверджено.', + 'resend_verification' => 'Натисніть тут, щоб надіслати лист підтвердження ще раз.', + 'verification_sent' => 'Нове посилання для підтвердження надіслано на вашу адресу email.', + 'save' => 'Зберегти', + ], + + 'authentication' => [ + 'title' => 'Автентифікація', + 'page_title' => 'Налаштування автентифікації', + 'sessions' => [ + 'title' => 'Активні сесії', + 'description' => 'Якщо помітили щось підозріле, вийдіть з інших пристроїв.', + 'unknown_browser' => 'Невідомий браузер', + 'unknown_ip' => 'Невідомий IP', + 'on' => 'на', + 'active_now' => 'Активна зараз', + 'log_out_others' => 'Вийти з інших пристроїв', + 'modal_title' => 'Вийти з інших пристроїв', + 'modal_description_password' => 'Введіть поточний пароль, щоб підтвердити вихід з інших сесій браузера.', + 'modal_description_email' => 'Введіть адресу email, щоб підтвердити вихід з інших сесій браузера.', + 'password_placeholder' => 'Поточний пароль', + 'email_placeholder' => 'Email вашого облікового запису', + 'cancel' => 'Скасувати', + 'submit' => 'Вийти з інших пристроїв', + 'email_mismatch' => 'Адреса email не збігається з вашим обліковим записом.', + 'flash_logged_out' => 'Ви вийшли з інших пристроїв.', + ], + 'password' => [ + 'update_title' => 'Оновити пароль', + 'set_title' => 'Встановити пароль', + 'update_description' => 'Переконайтеся, що ваш обліковий запис використовує довгий випадковий пароль для безпеки.', + 'set_description' => 'Додайте пароль, щоб входити без підключеного провайдера.', + 'current_password' => 'Поточний пароль', + 'new_password' => 'Новий пароль', + 'confirm_password' => 'Підтвердіть пароль', + 'save' => 'Зберегти пароль', + 'set' => 'Встановити пароль', + ], + 'providers' => [ + 'title' => 'Підключені акаунти', + 'description' => 'Входьте швидше через ці підключені провайдери.', + 'connected' => 'Підключено', + 'not_connected' => 'Не підключено', + 'connect' => 'Підключити', + 'disconnect' => 'Від’єднати', + 'flash_disconnected' => ':provider успішно від’єднано.', + 'flash_connected' => ':provider успішно підключено.', + 'flash_already_linked' => 'Цей акаунт :provider уже прив’язано до іншого користувача.', + 'flash_cannot_disconnect' => 'Ви не можете від’єднати свій єдиний спосіб входу. Спочатку встановіть пароль або підключіть іншого провайдера.', + ], + ], + + 'delete_account' => [ + 'heading' => 'Видалити обліковий запис', + 'description' => 'Видаліть обліковий запис і всі його ресурси', + 'warning' => 'Увага', + 'warning_message' => 'Будьте обережні: цю дію не можна скасувати. Якщо ви власник цього облікового запису, запрошених учасників також буде назавжди видалено.', + 'button' => 'Видалити обліковий запис', + 'modal_title' => 'Ви впевнені, що хочете видалити обліковий запис?', + 'modal_description_password' => 'Після видалення облікового запису всі його ресурси та дані також будуть назавжди видалені. Якщо ви власник облікового запису, запрошених учасників також буде назавжди видалено. Введіть пароль для підтвердження.', + 'modal_description_email' => 'Після видалення облікового запису всі його ресурси та дані також будуть назавжди видалені. Якщо ви власник облікового запису, запрошених учасників також буде назавжди видалено. Введіть свою адресу email :email для підтвердження.', + 'password' => 'Пароль', + 'password_placeholder' => 'Пароль', + 'email_placeholder' => 'Email вашого облікового запису', + 'email_mismatch' => 'Адреса email не збігається з вашим обліковим записом.', + 'cancel' => 'Скасувати', + 'confirm' => 'Видалити обліковий запис', + ], + + 'workspace' => [ + 'tabs' => [ + 'workspace' => 'Робочий простір', + 'brand' => 'Бренд', + 'users' => 'Учасники', + 'api_keys' => 'API-ключі', + ], + 'title' => 'Налаштування робочого простору', + 'logo_heading' => 'Логотип робочого простору', + 'logo_description' => 'Завантажте логотип для робочого простору', + 'heading' => 'Назва робочого простору', + 'description' => 'Оновіть назву робочого простору', + 'members_heading' => 'Учасники', + 'members_description' => 'Керуйте учасниками та запрошеннями робочого простору', + 'name' => 'Назва', + 'name_placeholder' => 'Мій робочий простір', + 'save' => 'Зберегти', + 'danger_description' => 'Незворотні дії.', + 'delete_warning' => 'Увага', + 'delete_title' => 'Видалити цей workspace', + 'delete_description' => 'Назавжди видаляє цей workspace разом із постами, підключеними акаунтами та медіа. З вас стягуватиметься оплата на один workspace менше.', + 'delete_description_self_hosted' => 'Назавжди видаляє цей workspace, його пости, підключені акаунти та медіа.', + 'delete_only_description' => 'Ви не можете видалити свій єдиний workspace. Скасуйте підписку в розділі оплати, щоб перестати платити, або видаліть обліковий запис в Автентифікації, щоб скасувати оплату та назавжди видалити все.', + 'delete_go_to_billing' => 'Перейти до оплати', + 'delete_go_to_delete_account' => 'Видалити обліковий запис', + 'delete_members_warning' => '{1}:count інший учасник втратить доступ. Учасники без іншого workspace у TryPost будуть назавжди видалені.|[2,*]:count інших учасників втратять доступ. Учасники без іншого workspace у TryPost будуть назавжди видалені.', + 'delete_action' => 'Видалити workspace', + 'delete_cancel' => 'Скасувати', + 'delete_confirm_title' => 'Видалити workspace?', + 'delete_confirm_description' => 'Це назавжди видалить workspace і всі його дані. Оплату буде оновлено з урахуванням одного workspace менше.', + 'delete_confirm_description_self_hosted' => 'Це назавжди видалить workspace і всі його дані.', + ], + + 'brand' => [ + 'title' => 'Бренд', + 'description' => 'Налаштуйте ідентичність бренду для AI-контенту.', + 'name' => 'Назва робочого простору', + 'name_placeholder' => 'Мій бренд', + 'website' => 'Вебсайт', + 'website_placeholder' => 'https://yourbrand.com', + 'brand_description' => 'Опис', + 'brand_description_placeholder' => 'Розкажіть про ваш бренд, чим ви займаєтеся та хто ваша аудиторія...', + 'voice' => 'Голос бренду', + 'voice_description' => 'Виберіть риси, що визначають звучання вашого контенту.', + 'voice_group' => [ + 'pov' => 'Точка зору', + 'formality' => 'Формальність', + 'energy' => 'Енергія', + 'humor' => 'Гумор', + 'attitude' => 'Настрій', + 'warmth' => 'Теплота', + 'confidence' => 'Впевненість', + 'style' => 'Стиль', + ], + 'voice_trait' => [ + 'first_person' => 'Перша особа', + 'second_person' => 'Друга особа (ви)', + 'third_person' => 'Третя особа', + 'formal' => 'Формальний', + 'balanced' => 'Збалансований', + 'casual' => 'Неформальний', + 'calm' => 'Спокійний', + 'moderate' => 'Помірний', + 'enthusiastic' => 'Ентузіастичний', + 'vibrant' => 'Яскравий', + 'serious' => 'Серйозний', + 'dry' => 'Сухий / тонкий', + 'witty' => 'Дотепний', + 'playful' => 'Грайливий', + 'respectful' => 'Шанобливий', + 'even_handed' => 'Об’єктивний', + 'bold' => 'Сміливий', + 'provocative' => 'Провокаційний', + 'neutral' => 'Нейтральний', + 'friendly' => 'Дружній', + 'empathetic' => 'Емпатичний', + 'humble' => 'Скромний', + 'confident' => 'Впевнений', + 'assertive' => 'Наполегливий', + 'direct' => 'Прямий', + 'concise' => 'Стислий', + 'transparent' => 'Прозорий', + 'no_hype' => 'Без хайпу', + 'practical' => 'Практичний', + 'data_driven' => 'На основі даних', + 'storytelling' => 'Оповідний', + 'inspirational' => 'Надихаючий', + 'educational' => 'Освітній', + 'technical' => 'Технічний', + 'minimalist' => 'Мало emoji', + ], + 'brand_color' => 'Колір бренду', + 'background_color' => 'Колір фону', + 'text_color' => 'Колір тексту', + 'font' => 'Шрифт', + 'image_style' => 'Стиль зображення', + 'image_style_description' => 'Візуальний стиль для AI-слайдів та обкладинок постів.', + 'image_style_cinematic' => 'Кінематографічний', + 'image_style_illustration' => 'Ілюстрація', + 'image_style_isometric_3d' => 'Ізометричний', + 'image_style_cartoon' => 'Мультяшний', + 'image_style_typographic' => 'Типографічний', + 'image_style_infographic' => 'Інфографіка', + 'image_style_minimalist' => 'Мінімалістичний', + 'image_style_mockup' => 'Мокап', + 'content_language' => 'Мова контенту', + 'content_language_description' => 'Мова для AI-підписів, хештегів і будь-якого тексту всередині згенерованих зображень або відео.', + 'font_placeholder' => 'Виберіть шрифт…', + 'font_search' => 'Пошук шрифту…', + 'font_empty' => 'Шрифтів не знайдено.', + 'language_placeholder' => 'Виберіть мову…', + 'language_search' => 'Пошук мови…', + 'language_empty' => 'Мову не знайдено.', + + ], + + 'members' => [ + 'title' => 'Учасники', + 'heading' => 'Учасники команди', + 'description' => 'Керуйте учасниками та запрошеннями цього робочого простору', + + 'cancel' => 'Скасувати', + 'remove' => 'Видалити', + 'make_role' => 'Призначити :role', + + 'invite' => [ + 'title' => 'Запросити учасника', + 'description' => 'Надішліть email-запрошення, щоб додати співавторів', + 'email' => 'Email', + 'email_placeholder' => 'collaborator@email.com', + 'role' => 'Роль', + 'role_placeholder' => 'Виберіть роль', + 'submit' => 'Надіслати запрошення', + ], + + 'pending' => [ + 'title' => 'Очікуючі запрошення', + 'description' => 'Запрошення, що очікують прийняття', + 'empty' => 'Немає очікуючих запрошень', + ], + + 'list' => [ + 'title' => 'Учасники', + 'description' => 'Люди з доступом до цього робочого простору', + 'empty' => 'Немає учасників, окрім власника', + ], + + 'remove_modal' => [ + 'title' => 'Видалити учасника', + 'description' => 'Ви впевнені, що хочете видалити цього учасника з робочого простору? Він втратить доступ до всіх ресурсів робочого простору.', + 'action' => 'Видалити учасника', + ], + + 'cancel_invite_modal' => [ + 'title' => 'Скасувати запрошення', + 'description' => 'Ви впевнені, що хочете скасувати це запрошення?', + 'action' => 'Скасувати запрошення', + ], + + 'roles' => [ + 'owner' => 'Власник', + 'admin' => 'Адміністратор', + 'member' => 'Учасник', + 'viewer' => 'Глядач', + ], + + 'errors' => [ + 'invite_exists' => 'Запрошення для цієї адреси email уже існує.', + 'email_belongs_to_account' => 'Цей email уже належить іншому обліковому запису TryPost. Попросіть робочу адресу email.', + ], + + 'flash' => [ + 'invite_sent' => 'Запрошення успішно надіслано!', + 'invite_deleted' => 'Запрошення видалено.', + 'member_removed' => 'Учасника успішно видалено.', + 'role_updated' => 'Роль учасника оновлено.', + 'wrong_email' => 'Це запрошення для іншої адреси email.', + 'already_member' => 'Ви вже є учасником цього робочого простору.', + 'invite_accepted' => 'Ласкаво просимо! Тепер ви учасник робочого простору.', + 'invite_workspace_gone' => 'Це запрошення більше не дійсне, оскільки робочий простір було видалено.', + 'invite_declined' => 'Запрошення відхилено.', + ], + ], + + 'account' => [ + 'tabs' => [ + 'account' => 'Обліковий запис', + 'usage' => 'Використання', + 'billing' => 'Оплата', + ], + 'title' => 'Налаштування облікового запису', + 'description' => 'Керуйте назвою облікового запису та email для оплати', + 'name' => 'Назва облікового запису', + 'name_placeholder' => 'Моя компанія', + 'billing_email' => 'Email для оплати', + 'billing_email_placeholder' => 'billing@company.com', + 'billing_email_hint' => 'Цей email використовуватиметься для рахунків і повідомлень про оплату від Stripe.', + 'submit' => 'Зберегти', + ], + + 'flash' => [ + 'account_updated' => 'Обліковий запис успішно оновлено!', + 'profile_updated' => 'Профіль успішно оновлено!', + 'password_updated' => 'Пароль успішно оновлено!', + 'workspace_updated' => 'Налаштування успішно оновлено!', + 'photo_updated' => 'Фото успішно оновлено!', + 'photo_deleted' => 'Фото успішно видалено!', + 'delete_failed_billing' => 'Не вдалося скасувати підписку у платіжного провайдера. Нічого не видалено. Спробуйте ще раз або зверніться до підтримки.', + 'logo_updated' => 'Логотип успішно завантажено!', + 'logo_deleted' => 'Логотип успішно видалено!', + 'notifications_updated' => 'Налаштування сповіщень оновлено!', + ], + + 'api_keys' => [ + 'title' => 'API-ключі', + 'page_title' => 'API-ключі', + 'heading' => 'API-ключі', + 'description' => 'Керуйте API-ключами для програмного доступу до робочого простору.', + 'create' => 'Створити API-ключ', + 'copy' => 'Копіювати', + 'new_token_message' => 'Ваш новий API-ключ створено. Скопіюйте його зараз — ви більше не зможете його побачити.', + 'table' => [ + 'name' => 'Назва', + 'key' => 'Ключ', + 'status' => 'Статус', + 'expires' => 'Діє до', + 'last_used' => 'Останнє використання', + 'never' => 'Ніколи', + ], + 'actions' => [ + 'copy_id' => 'Копіювати ID API-ключа', + 'copy_id_success' => 'ID API-ключа скопійовано в буфер обміну', + 'delete' => 'Видалити', + ], + 'empty' => [ + 'title' => 'API-ключів ще немає', + 'description' => 'Створіть API-ключ для програмного доступу до робочого простору.', + ], + 'delete_modal' => [ + 'title' => 'Видалити API-ключ', + 'description' => 'Ви впевнені, що хочете видалити цей API-ключ? Усі додатки, що використовують цей ключ, негайно втратять доступ.', + 'action' => 'Видалити API-ключ', + ], + 'create_dialog' => [ + 'title' => 'Створити API-ключ', + 'description' => 'Створіть новий API-ключ для програмного доступу до робочого простору.', + 'name' => 'Назва', + 'name_placeholder' => 'напр. Production API Key', + 'expires' => 'Дата закінчення (необов’язково)', + 'expires_placeholder' => 'Без терміну дії', + 'submit' => 'Створити', + 'cancel' => 'Скасувати', + ], + 'flash' => [ + 'created' => 'API-ключ успішно створено!', + 'deleted' => 'API-ключ успішно видалено!', + ], + ], +]; diff --git a/lang/uk/sidebar.php b/lang/uk/sidebar.php new file mode 100644 index 000000000..08ed5216f --- /dev/null +++ b/lang/uk/sidebar.php @@ -0,0 +1,61 @@ + 'Робочі простори', + 'select_workspace' => 'Виберіть робочий простір', + 'create_workspace' => 'Створити робочий простір', + 'create_post' => 'Створити пост', + 'profile' => 'Профіль', + 'log_out' => 'Вийти', + + 'workspace' => 'Робочий простір: :name', + 'workspace_select' => 'Робочий простір: вибрати', + + 'theme' => 'Тема: :name', + 'theme_light' => 'Світла', + 'theme_dark' => 'Темна', + 'theme_system' => 'Системна', + + 'language' => 'Мова: :name', + 'language_select' => 'Мова: вибрати', + + 'groups' => [ + 'posts' => 'Пости', + 'workspace' => 'Робочий простір', + 'others' => 'Інше', + ], + + 'analytics' => 'Аналітика', + 'automations' => 'Автоматизації', + 'settings' => 'Налаштування', + + 'posts' => [ + 'calendar' => 'Календар', + 'all' => 'Усі', + 'scheduled' => 'Заплановані', + 'posted' => 'Опубліковані', + 'drafts' => 'Чернетки', + ], + + 'workspace' => [ + 'connections' => 'Підключення', + 'signatures' => 'Підписи', + 'labels' => 'Мітки', + 'assets' => 'Медіафайли', + 'api_keys' => 'API-ключі', + ], + + 'notifications' => 'Сповіщення', + 'mark_all_read' => 'Позначити все прочитаним', + 'mark_as_read' => 'Позначити прочитаним', + 'archive_all' => 'Архівувати все', + 'no_notifications' => 'Немає сповіщень', + + 'support' => [ + 'docs' => 'Документація', + 'referral' => 'Отримуйте 30% за рефералами', + 'stay_updated' => 'Слідкуйте за оновленнями', + ], +]; diff --git a/lang/uk/signatures.php b/lang/uk/signatures.php new file mode 100644 index 000000000..4f1e32513 --- /dev/null +++ b/lang/uk/signatures.php @@ -0,0 +1,59 @@ + 'Підписи', + 'description' => 'Створюйте багаторазові підписи для швидкого додавання до постів', + 'search' => 'Пошук підписів...', + 'new' => 'Новий підпис', + 'empty_title' => 'Підписів ще немає', + 'empty_description' => 'Створюйте підписи, щоб швидко додавати хештеги, посилання або будь-який текст, який ви повторюєте', + 'no_search_results' => 'Жоден підпис не відповідає вашому пошуку', + 'try_different_search' => 'Спробуйте інше ключове слово або очистіть пошук.', + 'table' => [ + 'name' => 'Назва', + 'content' => 'Вміст', + 'created_at' => 'Створено', + ], + + 'actions' => [ + 'edit' => 'Редагувати підпис', + 'delete' => 'Видалити підпис', + ], + + 'create' => [ + 'title' => 'Створити підпис', + 'description' => 'Дайте підпису назву та вміст для додавання (хештеги, посилання, власний текст — усе, що ви повторюєте).', + 'name' => 'Назва', + 'name_placeholder' => 'напр. Маркетинг, Подорожі, Підпис бренду', + 'content' => 'Вміст', + 'content_placeholder' => "#marketing #socialmedia\nДізнайтеся більше: https://yourbrand.com", + 'content_hint' => 'Хештеги, посилання, вступи, підписи — усе, що ви додаєте до постів.', + 'submit' => 'Створити підпис', + 'submitting' => 'Створення...', + ], + + 'edit' => [ + 'title' => 'Редагувати підпис', + 'description' => 'Оновіть назву та вміст цього підпису.', + 'name' => 'Назва', + 'name_placeholder' => 'напр. Маркетинг, Подорожі, Підпис бренду', + 'content' => 'Вміст', + 'content_placeholder' => "#marketing #socialmedia\nДізнайтеся більше: https://yourbrand.com", + 'content_hint' => 'Хештеги, посилання, вступи, підписи — усе, що ви додаєте до постів.', + 'submit' => 'Зберегти зміни', + 'submitting' => 'Збереження...', + ], + + 'delete' => [ + 'title' => 'Видалити підпис', + 'description' => 'Ви впевнені, що хочете видалити цей підпис? Цю дію не можна скасувати.', + 'confirm' => 'Видалити', + 'cancel' => 'Скасувати', + ], + + 'flash' => [ + 'created' => 'Підпис створено.', + 'updated' => 'Підпис оновлено.', + 'deleted' => 'Підпис видалено.', + ], +]; diff --git a/lang/uk/usage.php b/lang/uk/usage.php new file mode 100644 index 000000000..39bf104f7 --- /dev/null +++ b/lang/uk/usage.php @@ -0,0 +1,15 @@ + 'Використання', + + 'section_account' => 'Обліковий запис', + 'section_account_description' => 'Квоти та обмеження вашого тарифного плану.', + 'section_ai' => 'AI-кредити', + 'section_ai_description' => 'Кредити списуються під час використання AI-функцій. Вони оновлюються першого числа кожного місяця.', + + 'workspaces' => 'Робочі простори', + 'social_accounts' => 'Соціальні акаунти', + 'members' => 'Учасники', + 'credits' => 'Кредити', +]; diff --git a/lang/uk/validation.php b/lang/uk/validation.php new file mode 100644 index 000000000..310c8aae0 --- /dev/null +++ b/lang/uk/validation.php @@ -0,0 +1,200 @@ + 'Поле :attribute має бути прийняте.', + 'accepted_if' => 'Поле :attribute має бути прийняте, коли :other дорівнює :value.', + 'active_url' => 'Поле :attribute має містити коректну URL-адресу.', + 'after' => 'Поле :attribute має містити дату після :date.', + 'after_or_equal' => 'Поле :attribute має містити дату не раніше :date.', + 'alpha' => 'Поле :attribute має містити лише літери.', + 'alpha_dash' => 'Поле :attribute має містити лише літери, цифри, дефіси та підкреслення.', + 'alpha_num' => 'Поле :attribute має містити лише літери та цифри.', + 'any_of' => 'Поле :attribute недійсне.', + 'array' => 'Поле :attribute має бути масивом.', + 'ascii' => 'Поле :attribute має містити лише однобайтові латинські символи та знаки.', + 'before' => 'Поле :attribute має містити дату до :date.', + 'before_or_equal' => 'Поле :attribute має містити дату не пізніше :date.', + 'between' => [ + 'array' => 'Поле :attribute має містити від :min до :max елементів.', + 'file' => 'Розмір файлу в полі :attribute має бути від :min до :max кілобайт.', + 'numeric' => 'Поле :attribute має бути від :min до :max.', + 'string' => 'Кількість символів у полі :attribute має бути від :min до :max.', + ], + 'boolean' => 'Поле :attribute має мати значення true або false.', + 'can' => 'Поле :attribute містить недопустиме значення.', + 'confirmed' => 'Підтвердження поля :attribute не збігається.', + 'contains' => 'У полі :attribute відсутнє обов’язкове значення.', + 'current_password' => 'Невірний пароль.', + 'date' => 'Поле :attribute має містити коректну дату.', + 'date_equals' => 'Поле :attribute має містити дату, що дорівнює :date.', + 'date_format' => 'Поле :attribute має відповідати формату :format.', + 'decimal' => 'Поле :attribute має містити :decimal знаків після коми.', + 'declined' => 'Поле :attribute має бути відхилене.', + 'declined_if' => 'Поле :attribute має бути відхилене, коли :other дорівнює :value.', + 'different' => 'Поля :attribute та :other мають відрізнятися.', + 'digits' => 'Поле :attribute має містити :digits цифр.', + 'digits_between' => 'Поле :attribute має містити від :min до :max цифр.', + 'dimensions' => 'Поле :attribute має недопустимі розміри зображення.', + 'distinct' => 'Поле :attribute містить повторюване значення.', + 'doesnt_contain' => 'Поле :attribute не має містити жодного з таких значень: :values.', + 'doesnt_end_with' => 'Поле :attribute не має закінчуватися одним із таких значень: :values.', + 'doesnt_start_with' => 'Поле :attribute не має починатися з одного з таких значень: :values.', + 'email' => 'Поле :attribute має містити коректну адресу email.', + 'encoding' => 'Поле :attribute має бути в кодуванні :encoding.', + 'ends_with' => 'Поле :attribute має закінчуватися одним із таких значень: :values.', + 'enum' => 'Вибране значення для :attribute недійсне.', + 'exists' => 'Вибране значення для :attribute недійсне.', + 'extensions' => 'Поле :attribute має мати одне з таких розширень: :values.', + 'file' => 'Поле :attribute має бути файлом.', + 'filled' => 'Поле :attribute має мати значення.', + 'gt' => [ + 'array' => 'Поле :attribute має містити більше ніж :value елементів.', + 'file' => 'Розмір файлу в полі :attribute має бути більше :value кілобайт.', + 'numeric' => 'Поле :attribute має бути більше :value.', + 'string' => 'Кількість символів у полі :attribute має бути більше :value.', + ], + 'gte' => [ + 'array' => 'Поле :attribute має містити :value елементів або більше.', + 'file' => 'Розмір файлу в полі :attribute має бути більше або дорівнювати :value кілобайт.', + 'numeric' => 'Поле :attribute має бути більше або дорівнювати :value.', + 'string' => 'Кількість символів у полі :attribute має бути більше або дорівнювати :value.', + ], + 'hex_color' => 'Поле :attribute має містити коректний шістнадцятковий колір.', + 'image' => 'Поле :attribute має бути зображенням.', + 'in' => 'Вибране значення для :attribute недійсне.', + 'in_array' => 'Поле :attribute має існувати в :other.', + 'in_array_keys' => 'Поле :attribute має містити хоча б один з таких ключів: :values.', + 'integer' => 'Поле :attribute має бути цілим числом.', + 'ip' => 'Поле :attribute має містити коректну IP-адресу.', + 'ipv4' => 'Поле :attribute має містити коректну IPv4-адресу.', + 'ipv6' => 'Поле :attribute має містити коректну IPv6-адресу.', + 'json' => 'Поле :attribute має містити коректний рядок JSON.', + 'list' => 'Поле :attribute має бути списком.', + 'lowercase' => 'Поле :attribute має бути в нижньому регістрі.', + 'lt' => [ + 'array' => 'Поле :attribute має містити менше ніж :value елементів.', + 'file' => 'Розмір файлу в полі :attribute має бути менше :value кілобайт.', + 'numeric' => 'Поле :attribute має бути менше :value.', + 'string' => 'Кількість символів у полі :attribute має бути менше :value.', + ], + 'lte' => [ + 'array' => 'Поле :attribute не має містити більше ніж :value елементів.', + 'file' => 'Розмір файлу в полі :attribute має бути менше або дорівнювати :value кілобайт.', + 'numeric' => 'Поле :attribute має бути менше або дорівнювати :value.', + 'string' => 'Кількість символів у полі :attribute має бути менше або дорівнювати :value.', + ], + 'mac_address' => 'Поле :attribute має містити коректну MAC-адресу.', + 'max' => [ + 'array' => 'Поле :attribute не має містити більше ніж :max елементів.', + 'file' => 'Розмір файлу в полі :attribute не має перевищувати :max кілобайт.', + 'numeric' => 'Поле :attribute не має бути більше :max.', + 'string' => 'Кількість символів у полі :attribute не має перевищувати :max.', + ], + 'max_digits' => 'Поле :attribute не має містити більше ніж :max цифр.', + 'mimes' => 'Поле :attribute має бути файлом одного з типів: :values.', + 'mimetypes' => 'Поле :attribute має бути файлом одного з типів: :values.', + 'min' => [ + 'array' => 'Поле :attribute має містити щонайменше :min елементів.', + 'file' => 'Розмір файлу в полі :attribute має бути щонайменше :min кілобайт.', + 'numeric' => 'Поле :attribute має бути щонайменше :min.', + 'string' => 'Кількість символів у полі :attribute має бути щонайменше :min.', + ], + 'min_digits' => 'Поле :attribute має містити щонайменше :min цифр.', + 'missing' => 'Поле :attribute має бути відсутнім.', + 'missing_if' => 'Поле :attribute має бути відсутнім, коли :other дорівнює :value.', + 'missing_unless' => 'Поле :attribute має бути відсутнім, якщо :other не дорівнює :value.', + 'missing_with' => 'Поле :attribute має бути відсутнім, коли присутнє :values.', + 'missing_with_all' => 'Поле :attribute має бути відсутнім, коли присутні :values.', + 'multiple_of' => 'Поле :attribute має бути кратним :value.', + 'not_in' => 'Вибране значення для :attribute недійсне.', + 'not_regex' => 'Формат поля :attribute недійсний.', + 'numeric' => 'Поле :attribute має бути числом.', + 'password' => [ + 'letters' => 'Поле :attribute має містити щонайменше одну літеру.', + 'mixed' => 'Поле :attribute має містити щонайменше одну велику та одну малу літеру.', + 'numbers' => 'Поле :attribute має містити щонайменше одну цифру.', + 'symbols' => 'Поле :attribute має містити щонайменше один символ.', + 'uncompromised' => 'Вказане значення :attribute з’являлося у витоку даних. Будь ласка, оберіть інше значення :attribute.', + ], + 'present' => 'Поле :attribute має бути присутнім.', + 'present_if' => 'Поле :attribute має бути присутнім, коли :other дорівнює :value.', + 'present_unless' => 'Поле :attribute має бути присутнім, якщо :other не дорівнює :value.', + 'present_with' => 'Поле :attribute має бути присутнім, коли присутнє :values.', + 'present_with_all' => 'Поле :attribute має бути присутнім, коли присутні :values.', + 'prohibited' => 'Поле :attribute заборонене.', + 'prohibited_if' => 'Поле :attribute заборонене, коли :other дорівнює :value.', + 'prohibited_if_accepted' => 'Поле :attribute заборонене, коли :other прийняте.', + 'prohibited_if_declined' => 'Поле :attribute заборонене, коли :other відхилене.', + 'prohibited_unless' => 'Поле :attribute заборонене, якщо :other не входить до :values.', + 'prohibits' => 'Поле :attribute забороняє наявність :other.', + 'regex' => 'Формат поля :attribute недійсний.', + 'required' => 'Поле :attribute обов’язкове.', + 'required_array_keys' => 'Поле :attribute має містити записи для: :values.', + 'required_if' => 'Поле :attribute обов’язкове, коли :other дорівнює :value.', + 'required_if_accepted' => 'Поле :attribute обов’язкове, коли :other прийняте.', + 'required_if_declined' => 'Поле :attribute обов’язкове, коли :other відхилене.', + 'required_unless' => 'Поле :attribute обов’язкове, якщо :other не входить до :values.', + 'required_with' => 'Поле :attribute обов’язкове, коли присутнє :values.', + 'required_with_all' => 'Поле :attribute обов’язкове, коли присутні :values.', + 'required_without' => 'Поле :attribute обов’язкове, коли :values відсутнє.', + 'required_without_all' => 'Поле :attribute обов’язкове, коли жодне з :values не присутнє.', + 'same' => 'Поле :attribute має збігатися з :other.', + 'size' => [ + 'array' => 'Поле :attribute має містити :size елементів.', + 'file' => 'Розмір файлу в полі :attribute має бути :size кілобайт.', + 'numeric' => 'Поле :attribute має дорівнювати :size.', + 'string' => 'Кількість символів у полі :attribute має дорівнювати :size.', + ], + 'starts_with' => 'Поле :attribute має починатися з одного з таких значень: :values.', + 'string' => 'Поле :attribute має бути рядком.', + 'timezone' => 'Поле :attribute має містити коректний часовий пояс.', + 'unique' => 'Таке значення поля :attribute вже зайняте.', + 'uploaded' => 'Не вдалося завантажити :attribute.', + 'uppercase' => 'Поле :attribute має бути у верхньому регістрі.', + 'url' => 'Поле :attribute має містити коректну URL-адресу.', + 'ulid' => 'Поле :attribute має містити коректний ULID.', + 'uuid' => 'Поле :attribute має містити коректний UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/lang/uk/workspaces.php b/lang/uk/workspaces.php new file mode 100644 index 000000000..50f2487dc --- /dev/null +++ b/lang/uk/workspaces.php @@ -0,0 +1,50 @@ + 'Робочі простори', + 'select_title' => 'Ваші робочі простори', + 'select_description' => 'Виберіть робочий простір, щоб продовжити', + 'current' => 'Поточний', + 'connections' => ':count підключень', + 'posts' => ':count постів', + + 'create' => [ + 'page_title' => 'Створіть свій робочий простір', + 'title' => 'Налаштуйте робочий простір', + 'description' => 'Розкажіть трохи про себе або свій проєкт. Ми використаємо це, щоб адаптувати AI-пости під ваш стиль.', + 'website' => 'Вебсайт', + 'website_placeholder' => 'https://yourbrand.com', + 'autofill' => 'Заповнити з вебсайту', + 'autofill_missing_url' => 'Спочатку введіть URL.', + 'autofill_success' => 'Інформацію про бренд завантажено.', + 'autofill_error' => 'Не вдалося автозаповнити. Ви можете заповнити поля вручну.', + 'autofill_errors' => [ + 'unreachable' => 'Не вдалося отримати доступ до цього вебсайту (:reason).', + 'http_status' => 'Вебсайт повернув неочікуваний статус (:status).', + 'invalid_scheme' => 'Підтримуються лише URL з http та https.', + 'missing_host' => 'У URL відсутній хост.', + 'unresolvable_host' => 'Не вдалося розв’язати хост (:host).', + 'private_network' => 'URL, що вказують на приватні мережі, не дозволені.', + ], + 'logo_captured' => 'Логотип отримано з вашого вебсайту.', + 'name' => 'Назва робочого простору', + 'name_placeholder' => 'напр. Acme Inc', + 'brand_description' => 'Опис бренду', + 'brand_description_placeholder' => 'Чим займається ваш бренд?', + 'content_language' => 'Мова контенту', + 'content_language_description' => 'AI-підписи будуть написані цією мовою.', + 'brand_color' => 'Колір бренду', + 'background_color' => 'Колір фону', + 'text_color' => 'Колір тексту', + 'submit' => 'Створити робочий простір', + 'success' => 'Робочий простір створено. Підключіть соціальний акаунт, щоб почати публікувати.', + ], + + 'cannot_delete_last' => 'Ви не можете видалити свій єдиний робочий простір. Скасуйте підписку в налаштуваннях оплати, щоб закрити обліковий запис.', + + 'flash' => [ + 'deleted' => 'Робочий простір успішно видалено.', + ], +]; diff --git a/resources/js/dayjs.ts b/resources/js/dayjs.ts index 1f4b1e8e1..235cdd773 100644 --- a/resources/js/dayjs.ts +++ b/resources/js/dayjs.ts @@ -13,6 +13,7 @@ import weekday from 'dayjs/plugin/weekday'; // Import locales import 'dayjs/locale/en'; +import 'dayjs/locale/uk'; import 'dayjs/locale/es'; import 'dayjs/locale/pt-br'; import 'dayjs/locale/fr'; @@ -42,7 +43,7 @@ dayjs.extend(weekday); dayjs.extend(isBetween); // Set Monday as first day of week (to match Carbon/Laravel) -const weekStartMonday = ['en', 'es', 'pt-br', 'fr', 'de', 'it', 'nl', 'pl', 'el', 'ja', 'ko', 'zh', 'ru', 'tr', 'ar']; +const weekStartMonday = ['en', 'uk', 'es', 'pt-br', 'fr', 'de', 'it', 'nl', 'pl', 'el', 'ja', 'ko', 'zh', 'ru', 'tr', 'ar']; weekStartMonday.forEach((locale) => dayjs.updateLocale(locale, { weekStart: 1 })); export default dayjs; diff --git a/tests/Feature/Ai/AutofillBrandTest.php b/tests/Feature/Ai/AutofillBrandTest.php index f0eec7c73..bb13d9f74 100644 --- a/tests/Feature/Ai/AutofillBrandTest.php +++ b/tests/Feature/Ai/AutofillBrandTest.php @@ -102,6 +102,7 @@ })->with([ // Every supported language, detected from its primary subtag. ['en', 'en'], + ['uk', 'uk'], ['pt', 'pt-BR'], ['es', 'es'], ['fr', 'fr'], @@ -119,6 +120,7 @@ // Region/script subtags still resolve to the supported language. ['pt-PT', 'pt-BR'], ['en-US', 'en'], + ['uk-UA', 'uk'], ['es-MX', 'es'], ['ja-JP', 'ja'], ['zh-Hans', 'zh'], diff --git a/tests/Feature/Middleware/SetLocaleTest.php b/tests/Feature/Middleware/SetLocaleTest.php index 1da03f036..be2f1bba9 100644 --- a/tests/Feature/Middleware/SetLocaleTest.php +++ b/tests/Feature/Middleware/SetLocaleTest.php @@ -38,7 +38,7 @@ function runSetLocale(?string $locale): string test('shares ltr direction for a left-to-right locale', function (string $locale) { expect(runSetLocale($locale))->toBe('ltr'); expect(app()->getLocale())->toBe($locale); -})->with(['en', 'ja', 'pt-BR', 'de']); +})->with(['en', 'uk', 'ja', 'pt-BR', 'de']); test('falls back to the default locale and ltr for an unknown cookie', function () { expect(runSetLocale('sv'))->toBe('ltr'); diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php index d9ea1a3a7..7778becf1 100644 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -78,6 +78,22 @@ $response->assertCookieNotExpired('locale'); }); +test('user can switch the UI locale to Ukrainian', function () { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->from(route('app.posts.index')) + ->put(route('app.profile.language'), [ + 'locale' => 'uk', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect(route('app.posts.index')) + ->assertCookieNotExpired('locale'); +}); + test('user cannot update locale with invalid code', function () { $user = User::factory()->create(); diff --git a/tests/Feature/WorkspaceControllerTest.php b/tests/Feature/WorkspaceControllerTest.php index aa09fb86d..55ad9a586 100644 --- a/tests/Feature/WorkspaceControllerTest.php +++ b/tests/Feature/WorkspaceControllerTest.php @@ -62,6 +62,7 @@ ->component('workspaces/Create', false) ->has('availableContentLanguages', count(ContentLanguage::cases())) ->where('availableContentLanguages.0', ['value' => 'en', 'label' => 'English', 'englishName' => 'English']) + ->where('availableContentLanguages.1', ['value' => 'uk', 'label' => 'Українська', 'englishName' => 'Ukrainian']) ); }); @@ -313,6 +314,7 @@ ->component('settings/workspace/Brand', false) ->has('workspace') ->has('availableContentLanguages', count(ContentLanguage::cases())) + ->where('availableContentLanguages.1', ['value' => 'uk', 'label' => 'Українська', 'englishName' => 'Ukrainian']) ); }); @@ -426,6 +428,18 @@ expect($this->workspace->refresh()->content_language)->toBe('fr'); }); +test('update workspace settings persists Ukrainian as a content language', function () { + $this->actingAs($this->user) + ->from(route('app.workspace.brand')) + ->put(route('app.workspace.settings.update'), [ + 'name' => $this->workspace->name, + 'content_language' => 'uk', + ])->assertRedirect(route('app.workspace.brand')) + ->assertSessionHasNoErrors(); + + expect($this->workspace->refresh()->content_language)->toBe('uk'); +}); + test('update workspace settings rejects an unsupported content language', function () { $this->actingAs($this->user) ->put(route('app.workspace.settings.update'), [ diff --git a/tests/Unit/Ai/Agents/PostContentGeneratorTest.php b/tests/Unit/Ai/Agents/PostContentGeneratorTest.php index b00476851..5a42c1568 100644 --- a/tests/Unit/Ai/Agents/PostContentGeneratorTest.php +++ b/tests/Unit/Ai/Agents/PostContentGeneratorTest.php @@ -4,9 +4,28 @@ use App\Ai\Agents\PostContentGenerator; use App\Enums\Ai\GeneratorFormat; +use App\Enums\Workspace\ContentLanguage; use App\Models\Workspace; use Illuminate\JsonSchema\JsonSchemaTypeFactory; +test('instructions use English for the default content language', function () { + $workspace = Workspace::factory()->make([ + 'content_language' => ContentLanguage::DEFAULT->value, + ]); + + expect((new PostContentGenerator(workspace: $workspace))->instructions()) + ->toContain('Write the output in the language with code: en.'); +}); + +test('instructions use Ukrainian when the workspace content language is uk', function () { + $workspace = Workspace::factory()->make([ + 'content_language' => ContentLanguage::Ukrainian->value, + ]); + + expect((new PostContentGenerator(workspace: $workspace))->instructions()) + ->toContain('Write the output in the language with code: uk.'); +}); + test('instructions render brand context', function () { $workspace = Workspace::factory()->make([ 'name' => 'TryPost', diff --git a/tests/Unit/Ai/Agents/PostContentHumanizerTest.php b/tests/Unit/Ai/Agents/PostContentHumanizerTest.php index 4d3763ff4..25f353481 100644 --- a/tests/Unit/Ai/Agents/PostContentHumanizerTest.php +++ b/tests/Unit/Ai/Agents/PostContentHumanizerTest.php @@ -36,3 +36,10 @@ expect($agent->instructions())->toContain('AI-tells'); }); + +test('instructions use Ukrainian when the workspace content language is uk', function () { + $workspace = Workspace::factory()->make(['content_language' => 'uk']); + + expect((new PostContentHumanizer(workspace: $workspace))->instructions()) + ->toContain('Output language: uk.'); +}); diff --git a/tests/Unit/Ai/Agents/PostContentReviewerTest.php b/tests/Unit/Ai/Agents/PostContentReviewerTest.php index a99413928..3586cbc85 100644 --- a/tests/Unit/Ai/Agents/PostContentReviewerTest.php +++ b/tests/Unit/Ai/Agents/PostContentReviewerTest.php @@ -22,6 +22,15 @@ expect($instructions)->toContain('pt-BR'); }); +test('instructions use Ukrainian when the workspace content language is uk', function () { + $workspace = Workspace::factory()->make([ + 'content_language' => 'uk', + ]); + + expect((new PostContentReviewer(workspace: $workspace))->instructions()) + ->toContain('Output language: uk.'); +}); + test('schema requires suggestions array with original/suggestion/reason', function () { $workspace = Workspace::factory()->make(); $agent = new PostContentReviewer(workspace: $workspace); diff --git a/tests/Unit/Enums/PersonaTest.php b/tests/Unit/Enums/PersonaTest.php index b0eb2c382..def0c2fe7 100644 --- a/tests/Unit/Enums/PersonaTest.php +++ b/tests/Unit/Enums/PersonaTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); use App\Enums\User\Persona; +use App\Enums\Workspace\ContentLanguage; test('persona values are stable', function () { expect(array_map(fn (Persona $persona): string => $persona->value, Persona::cases())) @@ -15,4 +16,4 @@ expect(__($key, [], $locale))->not->toBe($key); } -})->with(['en', 'es', 'pt-BR']); +})->with(ContentLanguage::values()); diff --git a/tests/Unit/Enums/Workspace/ContentLanguageTest.php b/tests/Unit/Enums/Workspace/ContentLanguageTest.php index b816c52e7..0e9a9e6fa 100644 --- a/tests/Unit/Enums/Workspace/ContentLanguageTest.php +++ b/tests/Unit/Enums/Workspace/ContentLanguageTest.php @@ -6,12 +6,12 @@ test('values exposes every supported content-language code', function () { expect(ContentLanguage::values())->toBe([ - 'en', 'pt-BR', 'es', 'fr', 'de', 'it', 'nl', + 'en', 'uk', 'pt-BR', 'es', 'fr', 'de', 'it', 'nl', 'pl', 'el', 'ja', 'ko', 'zh', 'ru', 'tr', 'ar', ]); }); -test('default language is English', function () { +test('default content language is English', function () { expect(ContentLanguage::DEFAULT)->toBe(ContentLanguage::English); expect(ContentLanguage::DEFAULT->value)->toBe('en'); }); @@ -21,6 +21,7 @@ expect($options)->toHaveCount(count(ContentLanguage::cases())); expect($options[0])->toBe(['value' => 'en', 'label' => 'English', 'englishName' => 'English']); + expect($options)->toContain(['value' => 'uk', 'label' => 'Українська', 'englishName' => 'Ukrainian']); expect($options)->toContain(['value' => 'pt-BR', 'label' => 'Português (Brasil)', 'englishName' => 'Brazilian Portuguese']); expect($options)->toContain(['value' => 'ja', 'label' => '日本語', 'englishName' => 'Japanese']); }); @@ -29,6 +30,7 @@ expect($language->englishName())->toBe($expected); })->with([ [ContentLanguage::English, 'English'], + [ContentLanguage::Ukrainian, 'Ukrainian'], [ContentLanguage::PortugueseBrazil, 'Brazilian Portuguese'], [ContentLanguage::Spanish, 'Spanish'], [ContentLanguage::French, 'French'], @@ -55,6 +57,7 @@ expect($language->label())->toBe($expected); })->with([ [ContentLanguage::English, 'English'], + [ContentLanguage::Ukrainian, 'Українська'], [ContentLanguage::PortugueseBrazil, 'Português (Brasil)'], [ContentLanguage::Spanish, 'Español'], [ContentLanguage::French, 'Français'], @@ -87,6 +90,7 @@ ['pt', ContentLanguage::PortugueseBrazil], ['pt-PT', ContentLanguage::PortugueseBrazil], ['en-US', ContentLanguage::English], + ['uk-UA', ContentLanguage::Ukrainian], ['es-MX', ContentLanguage::Spanish], ['fr', ContentLanguage::French], ['ja-JP', ContentLanguage::Japanese], diff --git a/tests/Unit/Services/Ai/AiImageClientTest.php b/tests/Unit/Services/Ai/AiImageClientTest.php index 5b65f2bd8..6537aae9b 100644 --- a/tests/Unit/Services/Ai/AiImageClientTest.php +++ b/tests/Unit/Services/Ai/AiImageClientTest.php @@ -90,6 +90,15 @@ Image::assertGenerated(fn (ImagePrompt $prompt) => $prompt->contains('French')); }); +test('generate appends Ukrainian instruction when language is uk', function () { + Image::fake(); + + $client = new AiImageClient; + $client->generate(['x'], ImageStyle::Cinematic, language: 'uk'); + + Image::assertGenerated(fn (ImagePrompt $prompt) => $prompt->contains('Ukrainian')); +}); + test('generate defaults to English instruction when language is unsupported', function () { Image::fake(); diff --git a/tests/Unit/Settings/DeleteAccountCopyTest.php b/tests/Unit/Settings/DeleteAccountCopyTest.php index 6b9d29c5f..3fc683e23 100644 --- a/tests/Unit/Settings/DeleteAccountCopyTest.php +++ b/tests/Unit/Settings/DeleteAccountCopyTest.php @@ -13,6 +13,7 @@ */ $accountDeleteInvitedMemberMarkers = [ 'en' => 'invited members', + 'uk' => 'запрошених учасників', 'pt-BR' => 'membros convidados', 'es' => 'miembros invitados', 'fr' => 'membres invités', @@ -34,6 +35,7 @@ */ $workspaceDeleteConditionalMemberMarkers = [ 'en' => 'without another TryPost workspace', + 'uk' => 'без іншого workspace у TryPost', 'pt-BR' => 'sem outro workspace no TryPost', 'es' => 'sin otro workspace en TryPost', 'fr' => 'sans autre workspace TryPost',