From a1fa89710603c1399f0afdb9a4ed7afd2d82ed96 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 7 Aug 2026 19:34:43 -0400 Subject: [PATCH] Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor * Rename onboarding residual share test to progress. Co-authored-by: Cursor * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor * Move User firstName coverage into UserTest. Co-authored-by: Cursor * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor --------- Co-authored-by: Cursor --- .../Onboarding/ResolveOnboardingStatus.php | 462 ++++++++++++ app/Enums/PostHog/OnboardingEvent.php | 13 + app/Enums/SocialAccount/Platform.php | 21 + app/Enums/User/ReferralSource.php | 5 + app/Events/OnboardingStatusUpdated.php | 103 +++ .../Controllers/App/BillingController.php | 18 +- .../Controllers/App/OnboardingController.php | 95 +++ .../Controllers/Auth/SocialController.php | 11 +- .../Middleware/App/HandleInertiaRequests.php | 38 + app/Http/Middleware/App/SetLocale.php | 4 +- .../AuthUserResource.php | 1 + app/Models/AccessToken.php | 19 + app/Models/Account.php | 6 +- app/Models/Traits/HasAccount.php | 46 ++ app/Models/Traits/HasOnboarding.php | 92 +++ app/Models/User.php | 34 +- app/Observers/AccessTokenObserver.php | 56 ++ app/Observers/PostObserver.php | 37 +- app/Observers/SocialAccountObserver.php | 74 +- app/Passport/AuthCodeRepository.php | 15 +- app/Passport/AuthorizationController.php | 49 ++ app/Services/PostHogService.php | 3 +- ...nboarding_timestamps_to_accounts_table.php | 32 + ...dismissed_for_accounts_with_app_access.php | 39 + lang/ar/mcp.php | 6 +- lang/ar/onboarding.php | 75 +- lang/ar/sidebar.php | 35 +- lang/ar/welcome.php | 13 +- lang/de/mcp.php | 6 +- lang/de/onboarding.php | 75 +- lang/de/sidebar.php | 35 +- lang/de/welcome.php | 13 +- lang/el/mcp.php | 6 +- lang/el/onboarding.php | 75 +- lang/el/sidebar.php | 35 +- lang/el/welcome.php | 13 +- lang/en/mcp.php | 6 +- lang/en/onboarding.php | 75 +- lang/en/sidebar.php | 35 +- lang/en/welcome.php | 13 +- lang/es/mcp.php | 6 +- lang/es/onboarding.php | 75 +- lang/es/sidebar.php | 35 +- lang/es/welcome.php | 13 +- lang/fr/mcp.php | 6 +- lang/fr/onboarding.php | 75 +- lang/fr/sidebar.php | 35 +- lang/fr/welcome.php | 13 +- lang/it/mcp.php | 6 +- lang/it/onboarding.php | 75 +- lang/it/sidebar.php | 35 +- lang/it/welcome.php | 13 +- lang/ja/mcp.php | 6 +- lang/ja/onboarding.php | 75 +- lang/ja/sidebar.php | 35 +- lang/ja/welcome.php | 13 +- lang/ko/mcp.php | 6 +- lang/ko/onboarding.php | 75 +- lang/ko/sidebar.php | 35 +- lang/ko/welcome.php | 13 +- lang/nl/mcp.php | 6 +- lang/nl/onboarding.php | 75 +- lang/nl/sidebar.php | 35 +- lang/nl/welcome.php | 13 +- lang/pl/mcp.php | 6 +- lang/pl/onboarding.php | 75 +- lang/pl/sidebar.php | 35 +- lang/pl/welcome.php | 13 +- lang/pt-BR/mcp.php | 6 +- lang/pt-BR/onboarding.php | 75 +- lang/pt-BR/sidebar.php | 35 +- lang/pt-BR/welcome.php | 13 +- lang/ru/mcp.php | 6 +- lang/ru/onboarding.php | 75 +- lang/ru/sidebar.php | 35 +- lang/ru/welcome.php | 13 +- lang/tr/mcp.php | 6 +- lang/tr/onboarding.php | 75 +- lang/tr/sidebar.php | 35 +- lang/tr/welcome.php | 13 +- lang/uk/mcp.php | 6 +- lang/uk/onboarding.php | 75 +- lang/uk/sidebar.php | 35 +- lang/uk/welcome.php | 13 +- lang/zh/mcp.php | 6 +- lang/zh/onboarding.php | 75 +- lang/zh/sidebar.php | 35 +- lang/zh/welcome.php | 13 +- resources/js/components/AppSidebar.vue | 3 + .../js/components/WorkspaceMenuContent.vue | 3 +- .../accounts/NetworkConnectGrid.vue | 9 +- .../js/components/mcp/McpPrimarySetup.vue | 38 +- .../onboarding/OnboardingStepCard.vue | 67 ++ .../onboarding/SidebarOnboarding.vue | 99 +++ resources/js/components/settings/UsersTab.vue | 2 +- .../js/composables/useOnboardingLiveReload.ts | 53 ++ resources/js/layouts/mcp/AuthorizeLayout.vue | 46 ++ resources/js/pages/billing/Processing.vue | 15 +- resources/js/pages/mcp/Authorize.vue | 363 +++++----- resources/js/pages/mcp/AuthorizeError.vue | 30 + resources/js/pages/onboarding/Index.vue | 232 ++++++ resources/js/pages/settings/workspace/Mcp.vue | 63 +- resources/js/pages/welcome/ReferralSource.vue | 30 + resources/js/types/index.d.ts | 7 + routes/app.php | 7 +- .../ResolveOnboardingStatusTest.php | 674 ++++++++++++++++++ tests/Feature/BillingControllerTest.php | 76 ++ tests/Feature/Jobs/PostHog/TrackPostTest.php | 6 +- .../Mcp/OAuthAuthorizeAuthTokenTest.php | 157 ++++ tests/Feature/Mcp/OAuthRegistrationTest.php | 117 ++- .../Feature/McpOAuthWorkspaceBindingTest.php | 21 +- tests/Feature/Middleware/SetLocaleTest.php | 19 + ...OnboardingDismissBackfillMigrationTest.php | 87 +++ .../AccountOnboardingTimestampsTest.php | 34 + .../Onboarding/OnboardingControllerTest.php | 644 +++++++++++++++++ .../OnboardingProgressShareTest.php | 140 ++++ tests/Feature/SocialControllerTest.php | 17 + .../Feature/Welcome/WelcomeControllerTest.php | 7 +- tests/Pest.php | 23 + tests/Unit/Enums/PersonaTest.php | 4 +- tests/Unit/Enums/PlatformTest.php | 9 + .../Events/OnboardingStatusUpdatedTest.php | 259 +++++++ .../Models/Traits/HasAccountTraitTest.php | 39 + .../Models/Traits/HasOnboardingTraitTest.php | 104 +++ tests/Unit/Models/UserTest.php | 15 + .../OnboardingStatusBroadcastTest.php | 467 ++++++++++++ 126 files changed, 5742 insertions(+), 1581 deletions(-) create mode 100644 app/Actions/Onboarding/ResolveOnboardingStatus.php create mode 100644 app/Enums/PostHog/OnboardingEvent.php create mode 100644 app/Events/OnboardingStatusUpdated.php create mode 100644 app/Http/Controllers/App/OnboardingController.php create mode 100644 app/Models/Traits/HasAccount.php create mode 100644 app/Models/Traits/HasOnboarding.php create mode 100644 app/Observers/AccessTokenObserver.php create mode 100644 database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php create mode 100644 database/migrations/2026_07_29_183500_backfill_onboarding_dismissed_for_accounts_with_app_access.php create mode 100644 resources/js/components/onboarding/OnboardingStepCard.vue create mode 100644 resources/js/components/onboarding/SidebarOnboarding.vue create mode 100644 resources/js/composables/useOnboardingLiveReload.ts create mode 100644 resources/js/layouts/mcp/AuthorizeLayout.vue create mode 100644 resources/js/pages/mcp/AuthorizeError.vue create mode 100644 resources/js/pages/onboarding/Index.vue create mode 100644 tests/Feature/Actions/Onboarding/ResolveOnboardingStatusTest.php create mode 100644 tests/Feature/Mcp/OAuthAuthorizeAuthTokenTest.php create mode 100644 tests/Feature/Migrations/OnboardingDismissBackfillMigrationTest.php create mode 100644 tests/Feature/Onboarding/AccountOnboardingTimestampsTest.php create mode 100644 tests/Feature/Onboarding/OnboardingControllerTest.php create mode 100644 tests/Feature/Onboarding/OnboardingProgressShareTest.php create mode 100644 tests/Unit/Events/OnboardingStatusUpdatedTest.php create mode 100644 tests/Unit/Models/Traits/HasAccountTraitTest.php create mode 100644 tests/Unit/Models/Traits/HasOnboardingTraitTest.php create mode 100644 tests/Unit/Models/UserTest.php create mode 100644 tests/Unit/Observers/OnboardingStatusBroadcastTest.php diff --git a/app/Actions/Onboarding/ResolveOnboardingStatus.php b/app/Actions/Onboarding/ResolveOnboardingStatus.php new file mode 100644 index 00000000..dad60f72 --- /dev/null +++ b/app/Actions/Onboarding/ResolveOnboardingStatus.php @@ -0,0 +1,462 @@ + 'mcp_connected', + 'social' => 'social_connected', + 'first_post' => 'first_post_created', + ]; + + public function __construct( + private readonly PostHogService $postHog, + ) {} + + public static function totalSteps(): int + { + return count(self::STEPS); + } + + /** + * Pure read of activation checklist state. Safe for Inertia shared props. + * + * @return array{ + * mcp_connected: bool, + * social_connected: bool, + * first_post_created: bool, + * skipped_steps: list, + * all_complete: bool, + * show_progress: bool, + * completed_at: ?string, + * dismissed_at: ?string + * } + */ + public function handle(User $user): array + { + $account = $user->accountOrFail(); + + return $account->isOnboardingCompleted() + ? $this->completedStatus($account) + : $this->activeStatus($user, $account); + } + + /** + * Read checklist state, capture step analytics, and stamp completion when done. + * Call from observers / explicit mutations — not from shared Inertia props or GETs. + * + * @return array{ + * mcp_connected: bool, + * social_connected: bool, + * first_post_created: bool, + * skipped_steps: list, + * all_complete: bool, + * show_progress: bool, + * completed_at: ?string, + * dismissed_at: ?string + * } + */ + public function syncProgress(User $user): array + { + $account = $user->accountOrFail(); + + // Completed is read-only. Dismissed still runs below so a late MCP + // connect can clear a leftover mcp skip. + if ($account->isOnboardingCompleted()) { + return $this->handle($user); + } + + $this->clearMcpSkipIfConnected($account); + $status = $this->handle($user); + + if ($account->isOnboardingDismissed()) { + return $status; + } + + $this->captureCompletedSteps($user, $account, $status); + + $owner = $account->owner; + + if (! data_get($status, 'all_complete') || $owner === null) { + return $status; + } + + $this->markCompleted($owner); + $account->refresh(); + + if (! $account->isOnboardingCompleted()) { + return $status; + } + + return [ + ...$status, + 'show_progress' => false, + 'completed_at' => $account->onboarding_completed_at?->toIso8601String(), + ]; + } + + /** + * Sync checklist progress for an account actor, then broadcast progress updates. + * Used by observers after a step unlocks (post-commit). + */ + public function syncAndNotify(Account $account, ?string $actorId = null): void + { + $actor = $actorId !== null + ? User::query()->with('account')->find($actorId) + : null; + + $syncTarget = $actor?->belongsToAccount($account->id) + ? $actor + : $account->owner; + + if ($syncTarget !== null) { + $this->syncProgress($syncTarget); + $account->refresh(); + } + + if ($account->isOnboardingOpen()) { + OnboardingStatusUpdated::broadcastForAccount($account); + } + } + + /** + * Stamp account onboarding completion once and capture the funnel event. + * Owner-only — teammates unlock steps but cannot finish the account funnel. + */ + public function markCompleted(User $user): bool + { + if (! $user->isAccountOwner()) { + return false; + } + + $account = $user->accountOrFail(); + + if (! $account->isOnboardingOpen()) { + return false; + } + + $completedAt = now(); + + $updated = Account::query() + ->whereKey($account->id) + ->onboardingOpen() + ->update([ + 'onboarding_completed_at' => $completedAt, + 'updated_at' => $completedAt, + ]); + + if ($updated === 0) { + return false; + } + + $account->refresh(); + + $this->postHog->capture($user->id, OnboardingEvent::Completed->value, account: $account); + + // Every stamp path must clear progress banners account-wide. + OnboardingStatusUpdated::broadcastForAccount($account); + + return true; + } + + /** + * Skip the optional MCP step, then stamp completion when it was the last open step. + */ + public function skipMcp(User $user): bool + { + $account = $user->accountOrFail(); + + if (! $account->isOnboardingOpen()) { + return false; + } + + $status = $this->handle($user); + + if ($this->stepIsSettled($status, 'mcp')) { + return false; + } + + $skippedSteps = [...data_get($status, 'skipped_steps', []), 'mcp']; + + $updated = Account::query() + ->whereKey($account->id) + ->onboardingOpen() + ->where(function (Builder $query): void { + $query->whereNull('onboarding_skipped_steps') + ->orWhereJsonDoesntContain('onboarding_skipped_steps', 'mcp'); + }) + ->update([ + 'onboarding_skipped_steps' => $skippedSteps, + 'updated_at' => now(), + ]); + + if ($updated === 0) { + return false; + } + + $account->refresh(); + + $this->postHog->capture( + $user->id, + OnboardingEvent::StepSkipped->value, + ['step' => 'mcp'], + $account, + ); + + $this->completeChecklistOrBroadcast($user, $account); + + return true; + } + + /** + * Sidebar checklist progress, or false when the banner should not show. + * + * Pure read — safe for Inertia shared props and prefetch. Cross-workspace + * completion is stamped by syncProgress / observers, not here. + * + * @return array{completed: int, total: int}|false + */ + public function sidebarProgress(User $user): array|false + { + if (! $this->canShowProgress($user)) { + return false; + } + + $status = $this->handle($user); + + return data_get($status, 'show_progress') + ? [ + 'completed' => $this->completedStepCount($status), + 'total' => self::totalSteps(), + ] + : false; + } + + /** + * Cheap gates before handle()'s step queries. + * Non-owners never reach step state. + */ + public function canShowProgress(User $user, ?Account $account = null): bool + { + if (! $user->isAccountOwner()) { + return false; + } + + $account ??= $user->account; + + return $account !== null + && $account->isOnboardingOpen() + && $account->hasAppAccess(); + } + + /** + * After skipping a step: stamp completion when the checklist is done, + * otherwise broadcast so sidebar progress refreshes. + */ + private function completeChecklistOrBroadcast(User $user, Account $account): void + { + if (data_get($this->handle($user), 'all_complete') && $this->markCompleted($user)) { + return; + } + + OnboardingStatusUpdated::broadcastForAccount($account); + } + + /** + * @return array{ + * mcp_connected: bool, + * social_connected: bool, + * first_post_created: bool, + * skipped_steps: list, + * all_complete: bool, + * show_progress: bool, + * completed_at: string, + * dismissed_at: ?string + * } + */ + private function completedStatus(Account $account): array + { + $skippedSteps = $account->skippedOnboardingSteps(); + + // Preserve skip vs real MCP completion for the ready UI badge. + $mcpConnected = ! $account->hasSkippedOnboardingStep('mcp') + || $account->hasOnboardingMcpConnection(); + + return [ + 'mcp_connected' => $mcpConnected, + 'social_connected' => true, + 'first_post_created' => true, + 'skipped_steps' => $mcpConnected + ? $this->withoutSkippedStep($skippedSteps, 'mcp') + : $skippedSteps, + 'all_complete' => true, + 'show_progress' => false, + 'completed_at' => $account->onboarding_completed_at->toIso8601String(), + 'dismissed_at' => $account->onboarding_dismissed_at?->toIso8601String(), + ]; + } + + /** + * @return array{ + * mcp_connected: bool, + * social_connected: bool, + * first_post_created: bool, + * skipped_steps: list, + * all_complete: bool, + * show_progress: bool, + * completed_at: null, + * dismissed_at: ?string + * } + */ + private function activeStatus(User $user, Account $account): array + { + $skippedSteps = $account->skippedOnboardingSteps(); + $steps = $this->stepStates($account); + + $allComplete = collect($steps)->every( + fn (bool $done, string $step): bool => $this->stepIsComplete($done, $step, $skippedSteps), + ); + + return [ + 'mcp_connected' => data_get($steps, 'mcp'), + 'social_connected' => data_get($steps, 'social'), + 'first_post_created' => data_get($steps, 'first_post'), + 'skipped_steps' => $skippedSteps, + 'all_complete' => $allComplete, + 'show_progress' => $this->canShowProgress($user, $account) && ! $allComplete, + 'completed_at' => null, + 'dismissed_at' => $account->onboarding_dismissed_at?->toIso8601String(), + ]; + } + + /** + * @return array{mcp: bool, social: bool, first_post: bool} + */ + private function stepStates(Account $account): array + { + return [ + 'mcp' => $account->hasOnboardingMcpConnection(), + 'social' => $this->accountHasSocialConnection($account), + 'first_post' => $this->accountHasPost($account), + ]; + } + + /** + * Whether a checklist step is already done or skipped in a status payload. + * + * @param array{mcp_connected?: bool, social_connected?: bool, first_post_created?: bool, skipped_steps?: list} $status + */ + private function stepIsSettled(array $status, string $step): bool + { + return $this->stepIsComplete( + (bool) data_get($status, data_get(self::STEPS, $step)), + $step, + data_get($status, 'skipped_steps', []), + ); + } + + /** + * @param list $skippedSteps + */ + private function stepIsComplete(bool $done, string $step, array $skippedSteps): bool + { + return $done || in_array($step, $skippedSteps, true); + } + + /** + * @param list $skippedSteps + * @return list + */ + private function withoutSkippedStep(array $skippedSteps, string $step): array + { + return array_values(array_diff($skippedSteps, [$step])); + } + + /** + * @param array{skipped_steps: list, mcp_connected: bool, social_connected: bool, first_post_created: bool} $status + */ + private function completedStepCount(array $status): int + { + return collect(self::STEPS) + ->filter(fn (string $statusKey, string $step): bool => $this->stepIsComplete( + (bool) data_get($status, $statusKey), + $step, + data_get($status, 'skipped_steps', []), + )) + ->count(); + } + + /** + * @param array{mcp_connected: bool, social_connected: bool, first_post_created: bool} $status + */ + private function captureCompletedSteps(User $user, Account $account, array $status): void + { + foreach (self::STEPS as $step => $statusKey) { + if (! data_get($status, $statusKey)) { + continue; + } + + $this->postHog->capture( + $user->id, + OnboardingEvent::StepCompleted->value, + ['step' => $step], + $account, + ); + } + } + + private function accountHasSocialConnection(Account $account): bool + { + return Workspace::query() + ->where('account_id', $account->id) + ->whereHas( + 'socialAccounts', + fn (Builder $query): Builder => $query->where('status', Status::Connected), + ) + ->exists(); + } + + private function accountHasPost(Account $account): bool + { + return Workspace::query() + ->where('account_id', $account->id) + ->whereHas('posts') + ->exists(); + } + + private function clearMcpSkipIfConnected(Account $account): void + { + if (! $account->hasSkippedOnboardingStep('mcp') || ! $account->hasOnboardingMcpConnection()) { + return; + } + + Account::query() + ->whereKey($account->id) + ->whereNull('onboarding_completed_at') + ->update([ + 'onboarding_skipped_steps' => $this->withoutSkippedStep( + $account->skippedOnboardingSteps(), + 'mcp', + ) ?: null, + 'updated_at' => now(), + ]); + + $account->refresh(); + } +} diff --git a/app/Enums/PostHog/OnboardingEvent.php b/app/Enums/PostHog/OnboardingEvent.php new file mode 100644 index 00000000..d09f9691 --- /dev/null +++ b/app/Enums/PostHog/OnboardingEvent.php @@ -0,0 +1,13 @@ + + */ + public static function connectableOptions(): array + { + return collect(self::cases()) + ->filter(fn (self $platform): bool => $platform->isConnectable()) + ->sortBy(fn (self $platform): string => mb_strtolower($platform->label())) + ->map(fn (self $platform): array => [ + 'value' => $platform->value, + 'label' => $platform->label(), + 'color' => $platform->color(), + 'network' => $platform->network(), + ]) + ->values() + ->all(); + } + /** * Static, platform-specific data exposed to the frontend (e.g. TikTok privacy options, * compliance URLs). Returns an empty array for platforms with no extra config. diff --git a/app/Enums/User/ReferralSource.php b/app/Enums/User/ReferralSource.php index 11f3b9df..3cec1d83 100644 --- a/app/Enums/User/ReferralSource.php +++ b/app/Enums/User/ReferralSource.php @@ -12,10 +12,15 @@ enum ReferralSource: string case YouTube = 'youtube'; case TikTok = 'tiktok'; case Instagram = 'instagram'; + case Threads = 'threads'; case Reddit = 'reddit'; case ProductHunt = 'product_hunt'; + case GitHub = 'github'; + case HackerNews = 'hacker_news'; + case Directories = 'directories'; case AiAssistant = 'ai_assistant'; case Friend = 'friend'; + case Founder = 'founder'; case Blog = 'blog'; case Other = 'other'; } diff --git a/app/Events/OnboardingStatusUpdated.php b/app/Events/OnboardingStatusUpdated.php new file mode 100644 index 00000000..84233931 --- /dev/null +++ b/app/Events/OnboardingStatusUpdated.php @@ -0,0 +1,103 @@ +workspaces()->pluck('id') as $workspaceId) { + static::dispatch((string) $workspaceId); + } + } + + /** + * Sync progress then broadcast to every workspace on the account. + * Use when a step is account-scoped (e.g. MCP OAuth). + */ + public static function dispatchForAccount(Account $account, ?User $actor = null): void + { + if ($account->isOnboardingOpen()) { + static::syncAfterCommit($account->id, $actor?->id); + } + } + + /** + * Sync progress for the workspace account, then broadcast. + * Actor-less flows (webhooks/jobs) fall back to the account owner. + */ + public static function dispatchForWorkspace(?string $workspaceId, ?User $actor = null): void + { + $account = filled($workspaceId) + ? Workspace::query()->find($workspaceId)?->account + : null; + + if ($account?->isOnboardingOpen()) { + static::syncAfterCommit($account->id, $actor?->id); + } + } + + private static function syncAfterCommit(string $accountId, ?string $actorId): void + { + DB::afterCommit(function () use ($accountId, $actorId): void { + $account = Account::query()->find($accountId); + + if ($account?->isOnboardingOpen()) { + app(ResolveOnboardingStatus::class)->syncAndNotify($account, $actorId); + } + }); + } + + public function broadcastAs(): string + { + return 'onboarding.status.updated'; + } + + /** + * @return array + */ + public function broadcastOn(): array + { + return [ + new PrivateChannel("workspace.{$this->workspaceId}"), + ]; + } + + /** + * @return array{workspace_id: string} + */ + public function broadcastWith(): array + { + return [ + 'workspace_id' => $this->workspaceId, + ]; + } + + public function broadcastQueue(): string + { + return 'broadcasts'; + } +} diff --git a/app/Http/Controllers/App/BillingController.php b/app/Http/Controllers/App/BillingController.php index 6773d693..efc92297 100644 --- a/app/Http/Controllers/App/BillingController.php +++ b/app/Http/Controllers/App/BillingController.php @@ -27,21 +27,27 @@ public function processing(Request $request): Response|RedirectResponse return redirect()->route('app.calendar'); } - $account = $request->user()->account; - $sessionId = $request->query('session_id'); + $user = $request->user(); + $account = $user->accountOrFail(); + $sessionId = $request->string('session_id')->toString(); // Consume the checkout session once: `fromCheckout` is true only the first // time this session_id is seen, so a back-button/refresh to the success URL // can't re-fire `checkout.completed`. `Cache::add` is atomic — it returns // true only when the key didn't exist yet. - $fromCheckout = is_string($sessionId) && $sessionId !== '' + $fromCheckout = filled($sessionId) && Cache::add("checkout_tracked:{$sessionId}", true, now()->addDay()); + $subscriptionActive = $account->subscribed(Account::SUBSCRIPTION_NAME); + $redirectToOnboarding = $user->isAccountOwner() + && $account->isOnboardingOpen(); + return Inertia::render('billing/Processing', [ - 'subscriptionActive' => $account && $account->subscribed(Account::SUBSCRIPTION_NAME), + 'subscriptionActive' => $subscriptionActive, 'fromCheckout' => $fromCheckout, - 'persona' => $request->user()->persona?->value, - 'conversion' => $fromCheckout && $account?->stripe_id + 'redirectToOnboarding' => $redirectToOnboarding, + 'persona' => $user->persona?->value, + 'conversion' => $fromCheckout && $account->stripe_id ? fn () => $this->buildConversionData($account, $sessionId) : null, ]); diff --git a/app/Http/Controllers/App/OnboardingController.php b/app/Http/Controllers/App/OnboardingController.php new file mode 100644 index 00000000..de66af80 --- /dev/null +++ b/app/Http/Controllers/App/OnboardingController.php @@ -0,0 +1,95 @@ +user(); + $workspace = $user->currentWorkspace; + $account = $user->accountOrFail(); + // Pure read on GET — observers + POST complete/skip stamp progress. + $status = $this->resolveOnboardingStatus->handle($user); + + if (data_get($status, 'dismissed_at') !== null) { + return redirect()->route('app.calendar'); + } + + if ( + $account->isOnboardingOpen() + && $user->isAccountOwner() + && ! data_get($status, 'all_complete') + ) { + $this->postHog->capture( + $user->id, + OnboardingEvent::Viewed->value, + account: $account, + ); + } + + return Inertia::render('onboarding/Index', [ + 'status' => fn (): array => $status, + 'canSkipSteps' => fn (): bool => $user->isAccountOwner(), + 'canManageAccounts' => fn (): bool => $user->can('manageAccounts', $workspace), + 'canCreatePost' => fn (): bool => $user->can('createPost', $workspace), + 'mcpUrl' => fn (): string => route('mcp.trypost'), + 'platforms' => fn (): array => SocialPlatform::connectableOptions(), + 'accounts' => fn (): array => SocialAccountResource::collection( + $workspace->socialAccounts()->orderBy('id')->get(), + )->resolve(), + ]); + } + + public function skipMcp(Request $request): RedirectResponse + { + abort_unless($request->user()->isAccountOwner(), Response::HTTP_FORBIDDEN); + + $this->resolveOnboardingStatus->skipMcp($request->user()); + + return back(); + } + + public function complete(Request $request): RedirectResponse + { + $user = $request->user(); + + abort_unless($user->isAccountOwner(), Response::HTTP_FORBIDDEN); + + $account = $user->accountOrFail(); + + if ($account->isOnboardingDismissed()) { + return redirect()->route('app.calendar'); + } + + if ($account->isOnboardingCompleted()) { + return redirect()->route('app.onboarding'); + } + + if (! data_get($this->resolveOnboardingStatus->handle($user), 'all_complete')) { + return redirect()->route('app.onboarding'); + } + + $this->resolveOnboardingStatus->markCompleted($user); + + return redirect()->route('app.onboarding'); + } +} diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index ec2ed978..d0c596eb 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -38,18 +38,9 @@ public function index(Request $request): Response $this->authorize('manageAccounts', $workspace); - $platforms = collect(SocialPlatform::cases()) - ->filter(fn ($platform) => $platform->isConnectable()) - ->map(fn ($platform) => [ - 'value' => $platform->value, - 'label' => $platform->label(), - 'color' => $platform->color(), - 'network' => $platform->network(), - ])->values(); - return Inertia::render('accounts/Index', [ 'workspace' => $workspace, - 'platforms' => $platforms, + 'platforms' => SocialPlatform::connectableOptions(), 'connectedAccounts' => SocialAccountResource::collection( $workspace->socialAccounts()->orderBy('id')->get(), )->resolve(), diff --git a/app/Http/Middleware/App/HandleInertiaRequests.php b/app/Http/Middleware/App/HandleInertiaRequests.php index 167185be..853a6083 100644 --- a/app/Http/Middleware/App/HandleInertiaRequests.php +++ b/app/Http/Middleware/App/HandleInertiaRequests.php @@ -4,12 +4,16 @@ namespace App\Http\Middleware\App; +use App\Actions\Onboarding\ResolveOnboardingStatus; use App\Enums\PostPlatform\ContentType; use App\Http\Resources\App\HandleInertiaRequests\AuthAccountResource; use App\Http\Resources\App\HandleInertiaRequests\AuthPlanResource; use App\Http\Resources\App\HandleInertiaRequests\AuthUserResource; use App\Http\Resources\App\HandleInertiaRequests\AuthWorkspaceResource; +use App\Models\User; use Illuminate\Http\Request; +use Inertia\DeferProp; +use Inertia\Inertia; use Inertia\Middleware; class HandleInertiaRequests extends Middleware @@ -48,6 +52,7 @@ public function share(Request $request): array ], 'usage' => $account && ! $isSelfHosted ? $account->usage() : null, 'features' => $account && ! $isSelfHosted ? $account->featureLimits() : null, + 'onboardingProgress' => $this->onboardingProgress($request, $user), 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'flash' => $request->session()->get('flash', []), 'applicationUrl' => config('app.url'), @@ -74,4 +79,37 @@ public function shareOnce(Request $request): array 'contentTypeMediaRules' => fn (): array => ContentType::mediaRulesForFrontend(), ]; } + + /** + * Defer step queries for mid-activation owners; everyone else gets false inline. + * + * Never defer on Passport consent *views*: Inertia deferred props re-request the + * same URL, Passport rotates `authToken` on every authorize hit, and approve then + * fails with InvalidAuthTokenException against the stale token still on the page. + */ + private function onboardingProgress(Request $request, ?User $user): DeferProp|false + { + if ($this->isPassportConsentViewRequest($request)) { + return false; + } + + $onboarding = app(ResolveOnboardingStatus::class); + + return $user && $onboarding->canShowProgress($user) + ? Inertia::defer(fn (): array|false => $onboarding->sidebarProgress($user)) + : false; + } + + /** + * Exact GET consent-view route names only — not approve/deny, and not wildcards + * like passport.authorizations.* (those would suppress defer on POST approve too, + * which is unnecessary and easy to misread as "all OAuth"). + */ + private function isPassportConsentViewRequest(Request $request): bool + { + return $request->routeIs( + 'passport.authorizations.authorize', + 'passport.device.authorizations.authorize', + ); + } } diff --git a/app/Http/Middleware/App/SetLocale.php b/app/Http/Middleware/App/SetLocale.php index a28cad1e..40afd5b9 100644 --- a/app/Http/Middleware/App/SetLocale.php +++ b/app/Http/Middleware/App/SetLocale.php @@ -29,8 +29,10 @@ public function handle(Request $request, Closure $next): Response $response = $next($request); + // Passport OAuth errors return a raw Symfony Response (no withCookie()). + // Attach via headers so both Illuminate and Symfony responses work. if (! $isValid) { - $response->withCookie( + $response->headers->setCookie( cookie()->forever('locale', config('languages.default'), '/', config('session.domain')), ); } diff --git a/app/Http/Resources/App/HandleInertiaRequests/AuthUserResource.php b/app/Http/Resources/App/HandleInertiaRequests/AuthUserResource.php index ca20b7d9..0ef97600 100644 --- a/app/Http/Resources/App/HandleInertiaRequests/AuthUserResource.php +++ b/app/Http/Resources/App/HandleInertiaRequests/AuthUserResource.php @@ -16,6 +16,7 @@ public static function make(User $user): array return [ 'id' => $user->id, 'name' => $user->name, + 'first_name' => $user->firstName(), 'email' => $user->email, 'has_photo' => $user->has_photo, 'photo_url' => $user->photo_url, diff --git a/app/Models/AccessToken.php b/app/Models/AccessToken.php index d686fedd..d202d189 100644 --- a/app/Models/AccessToken.php +++ b/app/Models/AccessToken.php @@ -4,10 +4,13 @@ namespace App\Models; +use App\Observers\AccessTokenObserver; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Laravel\Passport\Token; +#[ObservedBy(AccessTokenObserver::class)] class AccessToken extends Token { /** @@ -176,6 +179,22 @@ public function isActiveMcpGrant(): bool return $this->expires_at === null || ! $this->expires_at->isPast(); } + /** + * Bound MCP grant that unlocks the account onboarding checklist + * (account-owner grant + usable + createPost on the bound workspace). + */ + public function unlocksOnboardingChecklist(?User $user = null): bool + { + $user ??= $this->user; + $workspace = $this->workspace; + + return $user instanceof User + && $workspace instanceof Workspace + && $user->isAccountOwner() + && $this->isUsableMcpGrant($user, $workspace) + && $user->can('createPost', $workspace); + } + /** * Whether a refresh token can still mint a new access token for this grant. */ diff --git a/app/Models/Account.php b/app/Models/Account.php index 6834abe2..f082e6e3 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -4,6 +4,7 @@ namespace App\Models; +use App\Models\Traits\HasOnboarding; use App\Models\Traits\HasUsage; use Carbon\CarbonInterface; use Database\Factories\AccountFactory; @@ -19,7 +20,7 @@ class Account extends Model { /** @use HasFactory */ - use Billable, HasFactory, HasUsage, HasUuids; + use Billable, HasFactory, HasOnboarding, HasUsage, HasUuids; public const SUBSCRIPTION_NAME = 'default'; @@ -42,6 +43,9 @@ public static function postsCountCacheKey(string $accountId): string protected $casts = [ 'trial_ends_at' => 'datetime', + 'onboarding_completed_at' => 'datetime', + 'onboarding_dismissed_at' => 'datetime', + 'onboarding_skipped_steps' => 'array', ]; public function owner(): BelongsTo diff --git a/app/Models/Traits/HasAccount.php b/app/Models/Traits/HasAccount.php new file mode 100644 index 00000000..18ff131b --- /dev/null +++ b/app/Models/Traits/HasAccount.php @@ -0,0 +1,46 @@ +belongsTo(Account::class); + } + + /** + * Account for authenticated product flows (onboarding, billing, settings). + * In-app users always have one; null only during account teardown. + */ + public function accountOrFail(): Account + { + return $this->account ?? $this->account()->firstOrFail(); + } + + public function isAccountOwner(): bool + { + if (! $this->account_id) { + return false; + } + + if ($this->relationLoaded('account')) { + return $this->id === $this->account?->owner_id; + } + + return Account::query() + ->whereKey($this->account_id) + ->where('owner_id', $this->id) + ->exists(); + } + + public function belongsToAccount(string $accountId): bool + { + return $this->account_id === $accountId; + } +} diff --git a/app/Models/Traits/HasOnboarding.php b/app/Models/Traits/HasOnboarding.php new file mode 100644 index 00000000..cc75c42c --- /dev/null +++ b/app/Models/Traits/HasOnboarding.php @@ -0,0 +1,92 @@ +isOnboardingCompleted() || $this->isOnboardingDismissed(); + } + + public function isOnboardingCompleted(): bool + { + return $this->onboarding_completed_at !== null; + } + + public function isOnboardingDismissed(): bool + { + return $this->onboarding_dismissed_at !== null; + } + + /** + * Whether activation progress should still be tracked for this account. + * Prefer `$account?->isOnboardingOpen()` at call sites so null accounts bail out cleanly. + */ + public function isOnboardingOpen(): bool + { + return ! $this->hasFinishedOnboarding(); + } + + /** + * @return list + */ + public function skippedOnboardingSteps(): array + { + return $this->onboarding_skipped_steps ?? []; + } + + public function hasSkippedOnboardingStep(string $step): bool + { + return in_array($step, $this->skippedOnboardingSteps(), true); + } + + /** + * Whether the account owner has a bound MCP OAuth grant that unlocks + * the checklist. Teammate grants do not count — activation is owner-owned. + * Early-exits on the first matching grant so accounts with many MCP + * tokens do not pay for a full collection + Gate pass every time. + */ + public function hasOnboardingMcpConnection(): bool + { + if ($this->owner_id === null) { + return false; + } + + $tokens = AccessToken::query() + ->activeMcpOAuth() + ->where('user_id', $this->owner_id) + ->whereNotNull('workspace_id') + ->whereHas( + 'workspace', + fn (Builder $workspace): Builder => $workspace->where('account_id', $this->id), + ) + ->with(['user', 'workspace', 'client']) + ->lazyById(); + + foreach ($tokens as $token) { + if ($token->unlocksOnboardingChecklist()) { + return true; + } + } + + return false; + } + + /** + * @param Builder $query + * @return Builder + */ + public function scopeOnboardingOpen(Builder $query): Builder + { + return $query + ->whereNull('onboarding_completed_at') + ->whereNull('onboarding_dismissed_at'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 0395dd66..c730af1e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,24 +7,25 @@ use App\Enums\Notification\Type as NotificationType; use App\Enums\User\Persona; use App\Enums\User\ReferralSource; +use App\Models\Traits\HasAccount; use App\Models\Traits\HasMedia; use App\Models\Traits\HasWorkspace; use Database\Factories\UserFactory; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Str; use Laravel\Passport\Contracts\OAuthenticatable; use Laravel\Passport\HasApiTokens; class User extends Authenticatable implements MustVerifyEmail, OAuthenticatable { /** @use HasFactory */ - use HasApiTokens, HasFactory, HasMedia, HasUuids, HasWorkspace, Notifiable; + use HasAccount, HasApiTokens, HasFactory, HasMedia, HasUuids, HasWorkspace, Notifiable; /** * @var list @@ -74,6 +75,14 @@ public function getPhotoUrlAttribute(): ?string return $this->getFirstMediaUrl('avatar'); } + /** + * First whitespace-delimited token of the display name (empty when unset). + */ + public function firstName(): string + { + return (string) Str::of($this->name ?? '')->trim()->before(' '); + } + protected function casts(): array { return [ @@ -96,27 +105,6 @@ public function notificationPreference(): HasOne return $this->hasOne(NotificationPreference::class); } - public function account(): BelongsTo - { - return $this->belongsTo(Account::class); - } - - public function isAccountOwner(): bool - { - if (! $this->account_id) { - return false; - } - - if ($this->relationLoaded('account')) { - return $this->id === $this->account?->owner_id; - } - - return Account::query() - ->whereKey($this->account_id) - ->where('owner_id', $this->id) - ->exists(); - } - public function wantsEmailFor(NotificationType $type): bool { $preference = $this->notificationPreference; diff --git a/app/Observers/AccessTokenObserver.php b/app/Observers/AccessTokenObserver.php new file mode 100644 index 00000000..c1acc83f --- /dev/null +++ b/app/Observers/AccessTokenObserver.php @@ -0,0 +1,56 @@ +broadcastIfMcpOAuth($accessToken); + } + + public function updated(AccessToken $accessToken): void + { + if ($accessToken->wasChanged('revoked')) { + $this->broadcastIfMcpOAuth($accessToken); + } + } + + private function broadcastIfMcpOAuth(AccessToken $accessToken): void + { + if (! $this->looksLikeMcpOAuth($accessToken)) { + return; + } + + $user = User::query() + ->with(['account', 'currentWorkspace']) + ->find($accessToken->user_id); + + if ($user === null) { + return; + } + + // Revocation always notifies so progress can clear. Live grants only + // unlock when bound + createPost (viewers must not complete the checklist). + if ($accessToken->revoked || $accessToken->unlocksOnboardingChecklist($user)) { + OnboardingStatusUpdated::dispatchForAccount($user->accountOrFail(), $user); + } + } + + private function looksLikeMcpOAuth(AccessToken $accessToken): bool + { + // Ignore personal-access tokens; treat mid-revoke rows as MCP so + // disconnect still clears progress. + return in_array('mcp:use', $accessToken->scopes ?? [], true) + && ! $accessToken->isPersonalAccessToken(); + } +} diff --git a/app/Observers/PostObserver.php b/app/Observers/PostObserver.php index 1486cfc0..e70713e9 100644 --- a/app/Observers/PostObserver.php +++ b/app/Observers/PostObserver.php @@ -6,9 +6,12 @@ use App\Enums\Automation\Trigger\Type as TriggerType; use App\Enums\Post\Status as PostStatus; +use App\Events\OnboardingStatusUpdated; use App\Events\PostCreated; use App\Jobs\Automation\DispatchPostTriggerAutomationsJob; +use App\Models\Account; use App\Models\Post; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\DB; class PostObserver @@ -16,6 +19,13 @@ class PostObserver public function created(Post $post): void { DB::afterCommit(fn () => PostCreated::dispatch($post)); + + $this->notifyOnboarding($post); + } + + public function deleted(Post $post): void + { + $this->notifyOnboarding($post); } public function saved(Post $post): void @@ -30,10 +40,31 @@ public function saved(Post $post): void default => null, }; - if ($triggerType === null) { - return; + if ($triggerType !== null) { + DispatchPostTriggerAutomationsJob::dispatch($post, $triggerType)->afterCommit(); } + } - DispatchPostTriggerAutomationsJob::dispatch($post, $triggerType)->afterCommit(); + /** + * Notify only on the account's first post create or last post delete — + * later creates / partial deletes would spam Echo while activation is open. + */ + private function notifyOnboarding(Post $post): void + { + $account = $post->workspace?->account; + + if ($account?->isOnboardingOpen() && $this->otherPosts($account, $post)->doesntExist()) { + OnboardingStatusUpdated::dispatchForWorkspace($post->workspace_id, $post->user); + } + } + + /** + * @return Builder + */ + private function otherPosts(Account $account, Post $post): Builder + { + return Post::query() + ->whereIn('workspace_id', $account->workspaces()->select('id')) + ->whereKeyNot($post->id); } } diff --git a/app/Observers/SocialAccountObserver.php b/app/Observers/SocialAccountObserver.php index fb41f597..413bafdf 100644 --- a/app/Observers/SocialAccountObserver.php +++ b/app/Observers/SocialAccountObserver.php @@ -5,6 +5,8 @@ namespace App\Observers; use App\Enums\SocialAccount\Platform; +use App\Enums\SocialAccount\Status; +use App\Events\OnboardingStatusUpdated; use App\Exceptions\SocialAccount\NetworkAlreadyConnectedException; use App\Jobs\PostHog\SyncAccountUsage; use App\Models\SocialAccount; @@ -21,45 +23,83 @@ class SocialAccountObserver */ public function creating(SocialAccount $socialAccount): void { - if (config('trypost.self_hosted')) { - return; - } - - $platform = $socialAccount->platform; - - if (! $platform instanceof Platform) { + if (config('trypost.self_hosted') || ! $socialAccount->platform instanceof Platform) { return; } $conflict = SocialAccount::query() ->where('workspace_id', $socialAccount->workspace_id) - ->whereIn('platform', $platform->networkPlatformValues()) + ->whereIn('platform', $socialAccount->platform->networkPlatformValues()) ->exists(); if ($conflict) { - throw new NetworkAlreadyConnectedException($platform); + throw new NetworkAlreadyConnectedException($socialAccount->platform); } } public function created(SocialAccount $socialAccount): void { - $this->syncUsage($socialAccount); + $this->syncUsageAndOnboarding($socialAccount); } public function deleted(SocialAccount $socialAccount): void + { + $this->syncUsageAndOnboarding($socialAccount); + } + + public function updated(SocialAccount $socialAccount): void + { + if (! $socialAccount->wasChanged('status')) { + return; + } + + $wasConnected = $socialAccount->getRawOriginal('status') === Status::Connected->value; + $isConnected = $socialAccount->status === Status::Connected; + + if ($wasConnected !== $isConnected) { + $this->notifyOnboarding($socialAccount); + } + } + + private function syncUsageAndOnboarding(SocialAccount $socialAccount): void { $this->syncUsage($socialAccount); + + if ($socialAccount->status === Status::Connected) { + $this->notifyOnboarding($socialAccount); + } + } + + /** + * First usable connect / last disconnect for the account. + * Actor-less → syncAndNotify falls back to the account owner. + */ + private function notifyOnboarding(SocialAccount $socialAccount): void + { + $account = $socialAccount->workspace?->account; + + if (! $account?->isOnboardingOpen()) { + return; + } + + $hasOtherConnections = SocialAccount::query() + ->whereIn('workspace_id', $account->workspaces()->select('id')) + ->whereKeyNot($socialAccount->id) + ->where('status', Status::Connected) + ->exists(); + + if (! $hasOtherConnections) { + OnboardingStatusUpdated::dispatchForWorkspace($socialAccount->workspace_id); + } } private function syncUsage(SocialAccount $socialAccount): void { - if (! PostHogService::isEnabled()) { - return; + if (PostHogService::isEnabled()) { + SyncAccountUsage::dispatch( + (string) $socialAccount->workspace->account_id, + (string) $socialAccount->workspace_id, + ); } - - SyncAccountUsage::dispatch( - (string) $socialAccount->workspace->account_id, - (string) $socialAccount->workspace_id, - ); } } diff --git a/app/Passport/AuthCodeRepository.php b/app/Passport/AuthCodeRepository.php index f8c6e8fc..bb719560 100644 --- a/app/Passport/AuthCodeRepository.php +++ b/app/Passport/AuthCodeRepository.php @@ -10,6 +10,7 @@ use Laravel\Passport\Bridge\AuthCodeRepository as PassportAuthCodeRepository; use Laravel\Passport\Passport; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; /** * Persists the chosen workspace on the auth code so the subsequent token @@ -17,17 +18,27 @@ * * Requires an explicit `workspace_id` from the consent form (membership- * checked). No current-workspace fallback — silent re-consent is disabled - * so the picker always runs. + * so the picker always runs. Missing/invalid workspace fails here (during + * approve) instead of issuing a code that dies at token exchange. */ class AuthCodeRepository extends PassportAuthCodeRepository { public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void { + $workspaceId = $this->resolveWorkspaceId($authCodeEntity->getUserIdentifier()); + + if ($workspaceId === null) { + throw OAuthServerException::invalidRequest( + 'workspace_id', + 'Unable to bind this connection to a workspace. Reconnect from a workspace you belong to.', + ); + } + Passport::authCode()->forceFill([ 'id' => $authCodeEntity->getIdentifier(), 'user_id' => $authCodeEntity->getUserIdentifier(), 'client_id' => $authCodeEntity->getClient()->getIdentifier(), - 'workspace_id' => $this->resolveWorkspaceId($authCodeEntity->getUserIdentifier()), + 'workspace_id' => $workspaceId, 'scopes' => json_encode($authCodeEntity->getScopes()), 'revoked' => false, 'expires_at' => $authCodeEntity->getExpiryDateTime(), diff --git a/app/Passport/AuthorizationController.php b/app/Passport/AuthorizationController.php index 434d3ccb..64f2bd3e 100644 --- a/app/Passport/AuthorizationController.php +++ b/app/Passport/AuthorizationController.php @@ -5,9 +5,17 @@ namespace App\Passport; use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Http\Request; +use Inertia\Inertia; use Laravel\Passport\Client; +use Laravel\Passport\Contracts\AuthorizationViewResponse; +use Laravel\Passport\Exceptions\OAuthServerException; use Laravel\Passport\Http\Controllers\AuthorizationController as PassportAuthorizationController; use Laravel\Passport\Scope; +use League\OAuth2\Server\Exception\OAuthServerException as LeagueOAuthServerException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Response; /** * Always show the MCP consent screen so the user can pick a workspace. @@ -15,9 +23,50 @@ * Passport otherwise skips consent when the user already granted the same * scopes (silent re-consent), which would bind the auth code via * current_workspace without an explicit pick. + * + * Guests are sent to login before client validation so a stale MCP Inspector + * client_id does not return invalid_client JSON instead of the login page. + * Browser non-redirect OAuth failures get an Inertia error page; JSON clients + * and redirect errors (prompt=none) keep Passport's response. */ class AuthorizationController extends PassportAuthorizationController { + public function authorize( + ServerRequestInterface $psrRequest, + Request $request, + ResponseInterface $psrResponse, + AuthorizationViewResponse $viewResponse + ): Response|AuthorizationViewResponse { + if ($this->guard->guest()) { + $prompt = $request->string('prompt')->explode(' ')->map(trim(...))->filter()->values(); + + // prompt=none must not show a login UI — fall through to Passport. + if ($prompt->doesntContain('none')) { + $this->promptForLogin($request); + } + } + + try { + return parent::authorize($psrRequest, $request, $psrResponse, $viewResponse); + } catch (OAuthServerException $exception) { + if ($request->expectsJson() || $exception->getResponse()->isRedirection()) { + throw $exception; + } + + $oauth = $exception->getPrevious(); + + return Inertia::render('mcp/AuthorizeError', $oauth instanceof LeagueOAuthServerException + ? [ + 'error' => $oauth->getErrorType(), + 'errorDescription' => $oauth->getMessage(), + ] + : [ + 'error' => 'server_error', + 'errorDescription' => __('mcp.authorize.error_body'), + ])->toResponse($request); + } + } + /** * @param Scope[] $scopes */ diff --git a/app/Services/PostHogService.php b/app/Services/PostHogService.php index 074dbb58..54b38332 100644 --- a/app/Services/PostHogService.php +++ b/app/Services/PostHogService.php @@ -7,6 +7,7 @@ use App\Jobs\PostHog\SendEvent; use App\Models\Account; use Illuminate\Support\Facades\Log; +use Throwable; class PostHogService { @@ -78,7 +79,7 @@ private function dispatch(string $method, array $payload): void { try { SendEvent::dispatch($method, $payload); - } catch (\Throwable $e) { + } catch (Throwable $e) { Log::warning('PostHogService: failed to dispatch event', ['method' => $method, 'error' => $e->getMessage()]); } } diff --git a/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php b/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php new file mode 100644 index 00000000..0942f555 --- /dev/null +++ b/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php @@ -0,0 +1,32 @@ +timestamp('onboarding_completed_at')->nullable()->after('trial_ends_at'); + $table->timestamp('onboarding_dismissed_at')->nullable()->after('onboarding_completed_at'); + $table->json('onboarding_skipped_steps')->nullable()->after('onboarding_dismissed_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('accounts', function (Blueprint $table) { + $table->dropColumn(['onboarding_completed_at', 'onboarding_dismissed_at', 'onboarding_skipped_steps']); + }); + } +}; diff --git a/database/migrations/2026_07_29_183500_backfill_onboarding_dismissed_for_accounts_with_app_access.php b/database/migrations/2026_07_29_183500_backfill_onboarding_dismissed_for_accounts_with_app_access.php new file mode 100644 index 00000000..a891f243 --- /dev/null +++ b/database/migrations/2026_07_29_183500_backfill_onboarding_dismissed_for_accounts_with_app_access.php @@ -0,0 +1,39 @@ +whereNull('onboarding_dismissed_at') + ->whereNull('onboarding_completed_at') + ->update([ + 'onboarding_completed_at' => $now, + 'updated_at' => $now, + ]); + } + + public function down(): void + { + Account::query() + ->whereNull('onboarding_dismissed_at') + ->whereNotNull('onboarding_completed_at') + ->update([ + 'onboarding_completed_at' => null, + 'updated_at' => now(), + ]); + } +}; diff --git a/lang/ar/mcp.php b/lang/ar/mcp.php index c891b217..69745f53 100644 --- a/lang/ar/mcp.php +++ b/lang/ar/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'انسخ عنوان خادم TryPost', 'open_step' => 'افتح مساعد الذكاء الاصطناعي', 'copy' => 'نسخ الرابط', - 'connect' => 'الاتصال عبر :client', + 'connect' => 'اتصال :client', 'step_add' => 'الصق الاسم أو الرابط أو الإعداد أدناه في تطبيقك. يفتح تسجيل الدخول في المتصفح عند أول اتصال.', 'name_label' => 'الاسم', 'url_label' => 'رابط الخادم', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'فشل التفويض - TryPost', + 'error_title' => 'تعذر الاتصال', + 'error_body' => 'طلب التفويض هذا غير صالح أو منتهٍ. أغلق هذه النافذة وحاول الاتصال مرة أخرى من عميل MCP.', + 'error_code' => 'خطأ: :error', ], 'other_clients_title' => 'تطبيقات أخرى', diff --git a/lang/ar/onboarding.php b/lang/ar/onboarding.php index 3c69fa2a..5c6aca6d 100644 --- a/lang/ar/onboarding.php +++ b/lang/ar/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'مرحبًا بك في TryPost', - 'description' => 'أخبرنا بما يصفك أنت أو نشاطك التجاري على أفضل وجه حتى نتمكن من تخصيص تجربتك.', - 'continue' => 'متابعة', - 'personas' => [ - 'creator' => 'صانع محتوى', - 'freelancer' => 'مستقل', - 'developer' => 'مطوّر', - 'startup' => 'شركة ناشئة', - 'agency' => 'وكالة', - 'small_business' => 'نشاط تجاري صغير', - 'marketer' => 'مسوّق', - 'online_store' => 'متجر إلكتروني', - 'other' => 'أخرى', + 'title' => 'البدء', + 'welcome' => 'مرحبًا بك في TryPost، :name', + 'description' => 'اتبع الخطوات أدناه لمعرفة كيف يعمل TryPost ونشر منشورك الأول.', + 'skip_step' => 'تخطي هذه الخطوة', + 'continue' => 'المتابعة إلى TryPost', + 'status' => [ + 'complete' => 'مكتمل', + 'todo' => 'مطلوب', + 'skipped' => 'تم تخطيه', ], - 'goals_title' => 'ما هدفك من استخدام TryPost؟', - 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', - 'goals' => [ - 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', - 'ai_content' => 'إنشاء منشورات بذكاء TryPost الاصطناعي', - 'use_mcp' => 'إنشاء منشورات عبر Claude أو ChatGPT أو Cursor', - 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', - 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', - 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', - 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', - 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', - 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', - 'other' => 'شيء آخر', + 'mcp' => [ + 'title' => 'اربط مساعد الذكاء الاصطناعي', + 'description' => 'أضِف TryPost كخادم MCP ليتمكّن مساعدك من إنشاء منشورات التواصل وإدارتها نيابةً عنك.', + 'copied' => 'تم نسخ رابط MCP.', ], - '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' => 'مساعد ذكاء اصطناعي (ChatGPT، Claude…)', - 'friend' => 'صديق أو زميل', - 'blog' => 'مدونة أو نشرة إخبارية أو مقال', - 'other' => 'شيء آخر', + 'social' => [ + 'title' => 'اربط حسابًا اجتماعيًا', + 'description' => 'اختر شبكة واحدة واحدة على الأقل يمكن لـ TryPost النشر عليها.', + 'connected_elsewhere' => 'لقد ربطت حسابًا في مساحة عمل أخرى بالفعل، لذا اكتملت هذه الخطوة.', ], - 'connect' => [ - 'title' => 'اربط شبكتك الأولى', - 'description' => 'اربط حسابًا اجتماعيًا واحدًا على الأقل لبدء الجدولة. يمكنك إضافة المزيد في أي وقت.', - 'must_connect' => 'اربط شبكة واحدة على الأقل للمتابعة.', + 'first_post' => [ + 'title' => 'أنشئ منشورك الأول', + 'description' => 'جرّب هذا الموجّه مع مساعدك المتصل، أو أنشئ المنشور مباشرة في TryPost.', + 'prompt_label' => 'موجّه نموذجي', + 'sample_prompt' => 'أنشئ منشورًا اجتماعيًا ودّيًا يعرّف بعلامتي التجارية وكيّفه لكل شبكة متصلة.', + 'copy_prompt' => 'نسخ الموجّه', + 'copied' => 'تم نسخ الموجّه النموذجي.', + 'create_button' => 'إنشاء منشورك الأول', + 'or' => 'أو', + ], + 'ready' => [ + 'title' => 'أنت جاهز للنشر', + 'description' => 'كل شيء جاهز. تابع إلى TryPost وابدأ بتخطيط محتواك.', ], ]; diff --git a/lang/ar/sidebar.php b/lang/ar/sidebar.php index 0e468b80..c3d592ec 100644 --- a/lang/ar/sidebar.php +++ b/lang/ar/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'اختر مساحة العمل', 'create_workspace' => 'إنشاء مساحة عمل', 'create_post' => 'إنشاء منشور', - 'profile' => 'الملف الشخصي', 'my_account' => 'حسابي', 'account_settings' => 'الحساب والفوترة', 'workspace_settings' => 'إعدادات مساحة العمل', 'log_out' => 'تسجيل الخروج', - - 'workspace' => 'مساحة العمل: :name', - 'workspace_select' => 'مساحة العمل: اختر', - - 'theme' => 'المظهر: :name', - 'theme_light' => 'فاتح', - 'theme_dark' => 'داكن', - 'theme_system' => 'النظام', - + 'workspace' => [ + 'connections' => 'الاتصالات', + 'signatures' => 'التوقيعات', + 'labels' => 'التسميات', + 'assets' => 'الوسائط', + 'mcp' => 'MCP', + ], 'language' => 'اللغة: :name', - 'language_select' => 'اللغة: اختر', - 'groups' => [ 'posts' => 'المنشورات', 'workspace' => 'مساحة العمل', 'others' => 'أخرى', ], - 'analytics' => 'التحليلات', 'automations' => 'الأتمتة', - 'settings' => 'الإعدادات', - + 'onboarding' => 'البدء', + 'onboarding_hint' => 'أكمل الإعداد', 'posts' => [ 'calendar' => 'التقويم', 'all' => 'الكل', @@ -41,22 +35,11 @@ 'posted' => 'المنشورة', 'drafts' => 'المسودات', ], - - 'workspace' => [ - 'connections' => 'الاتصالات', - 'signatures' => 'التوقيعات', - 'labels' => 'التسميات', - 'assets' => 'الوسائط', - 'mcp' => 'MCP', - 'api_keys' => 'مفاتيح API', - ], - 'notifications' => 'الإشعارات', 'mark_all_read' => 'تعليم الكل كمقروء', 'mark_as_read' => 'تعليم كمقروء', 'archive_all' => 'أرشفة الكل', 'no_notifications' => 'لا توجد إشعارات', - 'support' => [ 'docs' => 'التوثيق', 'referral' => 'اربح عمولة إحالة 30%', diff --git a/lang/ar/welcome.php b/lang/ar/welcome.php index 04932d72..fd9e5616 100644 --- a/lang/ar/welcome.php +++ b/lang/ar/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'كيف وجدتنا؟', 'referral_source_description' => 'يساعدنا هذا على فهم كيفية اكتشاف الأشخاص لـ TryPost.', 'referral_source' => [ - 'google' => 'Google أو البحث', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram أو Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'مساعد ذكاء اصطناعي (ChatGPT، Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'دليل (G2، Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'صديق أو زميل', - 'blog' => 'مدونة أو نشرة إخبارية أو مقال', + 'founder' => 'المؤسس', + 'blog' => 'Blog / newsletter', 'other' => 'شيء آخر', ], ]; diff --git a/lang/de/mcp.php b/lang/de/mcp.php index ea3b2944..51af6218 100644 --- a/lang/de/mcp.php +++ b/lang/de/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Kopiere deine TryPost-Server-URL', 'open_step' => 'Öffne deinen KI-Assistenten', 'copy' => 'URL kopieren', - 'connect' => 'Mit :client verbinden', + 'connect' => ':client verbinden', 'step_add' => 'Füge Name, URL oder Config unten in deine App ein. Die Anmeldung öffnet sich beim ersten Verbinden im Browser.', 'name_label' => 'Name', 'url_label' => 'Server-URL', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Autorisierung fehlgeschlagen - TryPost', + 'error_title' => 'Verbindung fehlgeschlagen', + 'error_body' => 'Diese Autorisierungsanfrage ist ungültig oder abgelaufen. Schließen Sie dieses Fenster und verbinden Sie sich erneut über Ihren MCP-Client.', + 'error_code' => 'Fehler: :error', ], 'other_clients_title' => 'Andere Apps', diff --git a/lang/de/onboarding.php b/lang/de/onboarding.php index aad34eb0..1e91ebd0 100644 --- a/lang/de/onboarding.php +++ b/lang/de/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Willkommen bei TryPost', - 'description' => 'Sag uns, was dich oder dein Unternehmen am besten beschreibt, damit wir dein Erlebnis anpassen können.', - 'continue' => 'Weiter', - 'personas' => [ - 'creator' => 'Content Creator', - 'freelancer' => 'Freelancer', - 'developer' => 'Entwickler', - 'startup' => 'Startup', - 'agency' => 'Agentur', - 'small_business' => 'Kleinunternehmen', - 'marketer' => 'Marketer', - 'online_store' => 'Onlineshop', - 'other' => 'Sonstiges', + 'title' => 'Erste Schritte', + 'welcome' => 'Willkommen bei TryPost, :name', + 'description' => 'Folge den Schritten unten, um zu sehen, wie TryPost funktioniert, und deinen ersten Beitrag zu veröffentlichen.', + 'skip_step' => 'Diesen Schritt überspringen', + 'continue' => 'Weiter zu TryPost', + 'status' => [ + 'complete' => 'Erledigt', + 'todo' => 'Offen', + 'skipped' => 'Übersprungen', ], - 'goals_title' => 'Was ist dein Ziel mit TryPost?', - 'goals_description' => 'Wähle alles aus, was passt, und wir richten TryPost für dich ein.', - 'goals' => [ - 'save_time' => 'Zeit sparen, indem ich überall gleichzeitig poste', - 'ai_content' => 'Beiträge mit TryPost-KI erstellen', - 'use_mcp' => 'Beiträge über Claude, ChatGPT oder Cursor erstellen', - 'plan_calendar' => 'Meine Beiträge in einem Kalender planen', - 'stay_on_brand' => 'Jeden Beitrag markenkonform halten', - 'grow_audience' => 'Meine Reichweite und mein Engagement steigern', - 'drive_sales' => 'Mehr Traffic und Verkäufe erzielen', - 'manage_clients' => 'Mehrere Marken oder Kunden verwalten', - 'just_exploring' => 'Ich schaue mich vorerst nur um', - 'other' => 'Etwas anderes', + 'mcp' => [ + 'title' => 'Verbinde deinen KI-Assistenten', + 'description' => 'Füge TryPost als MCP-Server hinzu, damit dein Assistent Social-Beiträge für dich erstellen und verwalten kann.', + 'copied' => 'MCP-URL kopiert.', ], - 'referral_source_title' => 'Wie hast du uns gefunden?', - 'referral_source_description' => 'Das hilft uns zu verstehen, wie Menschen TryPost entdecken.', - 'referral_source' => [ - 'google' => 'Google oder Suche', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram oder Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'KI-Assistent (ChatGPT, Claude…)', - 'friend' => 'Freund oder Kollege', - 'blog' => 'Blog, Newsletter oder Artikel', - 'other' => 'Etwas anderes', + 'social' => [ + 'title' => 'Verbinde ein Social-Konto', + 'description' => 'Wähle mindestens ein Netzwerk, in dem TryPost deine Inhalte veröffentlichen kann.', + 'connected_elsewhere' => 'Du hast bereits ein Konto in einem anderen Workspace verbunden — dieser Schritt ist erledigt.', ], - 'connect' => [ - 'title' => 'Verbinde dein erstes Netzwerk', - 'description' => 'Verknüpfe mindestens ein Social-Media-Konto, um mit der Planung zu beginnen. Du kannst jederzeit weitere hinzufügen.', - 'must_connect' => 'Verbinde mindestens ein Netzwerk, um fortzufahren.', + 'first_post' => [ + 'title' => 'Erstelle deinen ersten Beitrag', + 'description' => 'Probiere diesen Starter-Prompt mit deinem verbundenen Assistenten aus, oder erstelle den Beitrag direkt in TryPost.', + 'prompt_label' => 'Beispiel-Prompt', + 'sample_prompt' => 'Erstelle einen freundlichen Social-Beitrag, der meine Marke vorstellt, und passe ihn für jedes verbundene Netzwerk an.', + 'copy_prompt' => 'Prompt kopieren', + 'copied' => 'Beispiel-Prompt kopiert.', + 'create_button' => 'Ersten Beitrag erstellen', + 'or' => 'oder', + ], + 'ready' => [ + 'title' => 'Du bist bereit zum Veröffentlichen', + 'description' => 'Alles klar. Weiter zu TryPost und plane deine Inhalte.', ], ]; diff --git a/lang/de/sidebar.php b/lang/de/sidebar.php index 0e6129f0..06c41f1c 100644 --- a/lang/de/sidebar.php +++ b/lang/de/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Workspace auswählen', 'create_workspace' => 'Workspace erstellen', 'create_post' => 'Beitrag erstellen', - 'profile' => 'Profil', 'my_account' => 'Mein Konto', 'account_settings' => 'Konto & Abrechnung', 'workspace_settings' => 'Workspace-Einstellungen', 'log_out' => 'Abmelden', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Auswählen', - - 'theme' => 'Design: :name', - 'theme_light' => 'Hell', - 'theme_dark' => 'Dunkel', - 'theme_system' => 'System', - + 'workspace' => [ + 'connections' => 'Verbindungen', + 'signatures' => 'Signaturen', + 'labels' => 'Labels', + 'assets' => 'Assets', + 'mcp' => 'MCP', + ], 'language' => 'Sprache: :name', - 'language_select' => 'Sprache: Auswählen', - 'groups' => [ 'posts' => 'Beiträge', 'workspace' => 'Workspace', 'others' => 'Sonstiges', ], - 'analytics' => 'Analytics', 'automations' => 'Automatisierungen', - 'settings' => 'Einstellungen', - + 'onboarding' => 'Erste Schritte', + 'onboarding_hint' => 'Einrichtung abschließen', 'posts' => [ 'calendar' => 'Kalender', 'all' => 'Alle', @@ -41,22 +35,11 @@ 'posted' => 'Veröffentlicht', 'drafts' => 'Entwürfe', ], - - 'workspace' => [ - 'connections' => 'Verbindungen', - 'signatures' => 'Signaturen', - 'labels' => 'Labels', - 'assets' => 'Assets', - 'mcp' => 'MCP', - 'api_keys' => 'API-Keys', - ], - 'notifications' => 'Benachrichtigungen', 'mark_all_read' => 'Alle als gelesen markieren', 'mark_as_read' => 'Als gelesen markieren', 'archive_all' => 'Alle archivieren', 'no_notifications' => 'Keine Benachrichtigungen', - 'support' => [ 'docs' => 'Dokumentation', 'referral' => '30% Provision verdienen', diff --git a/lang/de/welcome.php b/lang/de/welcome.php index 510408bd..e4f6f39d 100644 --- a/lang/de/welcome.php +++ b/lang/de/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Wie hast du uns gefunden?', 'referral_source_description' => 'Das hilft uns zu verstehen, wie Menschen TryPost entdecken.', 'referral_source' => [ - 'google' => 'Google oder Suche', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram oder Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'KI-Assistent (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Verzeichnis (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Freund oder Kollege', - 'blog' => 'Blog, Newsletter oder Artikel', + 'founder' => 'Gründer', + 'blog' => 'Blog / newsletter', 'other' => 'Etwas anderes', ], ]; diff --git a/lang/el/mcp.php b/lang/el/mcp.php index c6f1f684..ea4e71bf 100644 --- a/lang/el/mcp.php +++ b/lang/el/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Αντίγραψε το URL του διακομιστή TryPost', 'open_step' => 'Άνοιξε τον βοηθό AI σου', 'copy' => 'Αντιγραφή URL', - 'connect' => 'Σύνδεση με :client', + 'connect' => 'Σύνδεση :client', 'step_add' => 'Επικολλήστε το όνομα, το URL ή το config παρακάτω στην εφαρμογή σας. Η σύνδεση ανοίγει στο πρόγραμμα περιήγησης την πρώτη φορά.', 'name_label' => 'Όνομα', 'url_label' => 'URL διακομιστή', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Αποτυχία εξουσιοδότησης - TryPost', + 'error_title' => 'Αδυναμία σύνδεσης', + 'error_body' => 'Αυτό το αίτημα εξουσιοδότησης είναι μη έγκυρο ή έχει λήξει. Κλείστε αυτό το παράθυρο και δοκιμάστε ξανά από τον πελάτη MCP.', + 'error_code' => 'Σφάλμα: :error', ], 'other_clients_title' => 'Άλλες εφαρμογές', diff --git a/lang/el/onboarding.php b/lang/el/onboarding.php index 3d0799a0..be6f3319 100644 --- a/lang/el/onboarding.php +++ b/lang/el/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Καλώς ήρθατε στο TryPost', - 'description' => 'Πείτε μας τι σας περιγράφει καλύτερα, εσάς ή την επιχείρησή σας, ώστε να προσαρμόσουμε την εμπειρία σας.', - 'continue' => 'Συνέχεια', - 'personas' => [ - 'creator' => 'Δημιουργός περιεχομένου', - 'freelancer' => 'Ελεύθερος επαγγελματίας', - 'developer' => 'Προγραμματιστής', - 'startup' => 'Startup', - 'agency' => 'Πρακτορείο', - 'small_business' => 'Μικρή επιχείρηση', - 'marketer' => 'Marketer', - 'online_store' => 'Ηλεκτρονικό κατάστημα', - 'other' => 'Άλλο', + 'title' => 'Ξεκινώντας', + 'welcome' => 'Καλώς ήρθες στο TryPost, :name', + 'description' => 'Ακολούθησε τα παρακάτω βήματα για να δεις πώς λειτουργεί το TryPost και να δημοσιεύσεις την πρώτη σου ανάρτηση.', + 'skip_step' => 'Παράλειψη αυτού του βήματος', + 'continue' => 'Συνέχεια στο TryPost', + 'status' => [ + 'complete' => 'Ολοκληρώθηκε', + 'todo' => 'Εκκρεμεί', + 'skipped' => 'Παραλείφθηκε', ], - 'goals_title' => 'Ποιος είναι ο στόχος σας με το TryPost;', - 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', - 'goals' => [ - 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', - 'ai_content' => 'Δημιουργία δημοσιεύσεων με το AI του TryPost', - 'use_mcp' => 'Δημιουργία δημοσιεύσεων από Claude, ChatGPT ή Cursor', - 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', - 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', - 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', - 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', - 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', - 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', - 'other' => 'Κάτι άλλο', + 'mcp' => [ + 'title' => 'Σύνδεσε τον βοηθό AI σου', + 'description' => 'Πρόσθεσε το TryPost ως διακομιστή MCP ώστε ο βοηθός σου να δημιουργεί και να διαχειρίζεται social posts για εσένα.', + 'copied' => 'Το URL MCP αντιγράφηκε.', ], - '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' => 'Blog, newsletter ή άρθρο', - 'other' => 'Κάτι άλλο', + 'social' => [ + 'title' => 'Σύνδεσε έναν λογαριασμό social', + 'description' => 'Διάλεξε τουλάχιστον ένα δίκτυο όπου το TryPost μπορεί να δημοσιεύει το περιεχόμενό σου.', + 'connected_elsewhere' => 'Έχεις ήδη συνδέσει λογαριασμό σε άλλο workspace, οπότε αυτό το βήμα ολοκληρώθηκε.', ], - 'connect' => [ - 'title' => 'Συνδέστε το πρώτο σας δίκτυο', - 'description' => 'Συνδέστε τουλάχιστον έναν λογαριασμό κοινωνικού δικτύου για να ξεκινήσετε τον προγραμματισμό. Μπορείτε να προσθέσετε κι άλλους ανά πάσα στιγμή.', - 'must_connect' => 'Συνδέστε τουλάχιστον ένα δίκτυο για να συνεχίσετε.', + 'first_post' => [ + 'title' => 'Δημιούργησε την πρώτη σου ανάρτηση', + 'description' => 'Δοκίμασε αυτό το αρχικό prompt με τον συνδεδεμένο βοηθό σου ή δημιούργησε την ανάρτηση απευθείας στο TryPost.', + 'prompt_label' => 'Δείγμα prompt', + 'sample_prompt' => 'Δημιούργησε μια φιλική social ανάρτηση που παρουσιάζει το brand μου και προσαρμοσέ την για κάθε συνδεδεμένο δίκτυο.', + 'copy_prompt' => 'Αντιγραφή prompt', + 'copied' => 'Το δείγμα prompt αντιγράφηκε.', + 'create_button' => 'Δημιούργησε την πρώτη σου ανάρτηση', + 'or' => 'ή', + ], + 'ready' => [ + 'title' => 'Είσαι έτοιμος να δημοσιεύσεις', + 'description' => 'Όλα είναι έτοιμα. Συνέχισε στο TryPost και ξεκίνα να σχεδιάζεις το περιεχόμενό σου.', ], ]; diff --git a/lang/el/sidebar.php b/lang/el/sidebar.php index 8ce651f4..82164674 100644 --- a/lang/el/sidebar.php +++ b/lang/el/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Επιλογή workspace', 'create_workspace' => 'Δημιουργία workspace', 'create_post' => 'Δημιουργία δημοσίευσης', - 'profile' => 'Προφίλ', 'my_account' => 'Ο λογαριασμός μου', 'account_settings' => 'Λογαριασμός και χρέωση', 'workspace_settings' => 'Ρυθμίσεις workspace', 'log_out' => 'Αποσύνδεση', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Επιλογή', - - 'theme' => 'Θέμα: :name', - 'theme_light' => 'Φωτεινό', - 'theme_dark' => 'Σκοτεινό', - 'theme_system' => 'Συστήματος', - + 'workspace' => [ + 'connections' => 'Συνδέσεις', + 'signatures' => 'Υπογραφές', + 'labels' => 'Ετικέτες', + 'assets' => 'Στοιχεία', + 'mcp' => 'MCP', + ], 'language' => 'Γλώσσα: :name', - 'language_select' => 'Γλώσσα: Επιλογή', - 'groups' => [ 'posts' => 'Δημοσιεύσεις', 'workspace' => 'Workspace', 'others' => 'Άλλα', ], - 'analytics' => 'Στατιστικά', 'automations' => 'Αυτοματισμοί', - 'settings' => 'Ρυθμίσεις', - + 'onboarding' => 'Ξεκινώντας', + 'onboarding_hint' => 'Ολοκλήρωση ρύθμισης', 'posts' => [ 'calendar' => 'Ημερολόγιο', 'all' => 'Όλες', @@ -41,22 +35,11 @@ 'posted' => 'Δημοσιευμένες', 'drafts' => 'Πρόχειρα', ], - - 'workspace' => [ - 'connections' => 'Συνδέσεις', - 'signatures' => 'Υπογραφές', - 'labels' => 'Ετικέτες', - 'assets' => 'Στοιχεία', - 'mcp' => 'MCP', - 'api_keys' => 'Κλειδιά API', - ], - 'notifications' => 'Ειδοποιήσεις', 'mark_all_read' => 'Επισήμανση όλων ως αναγνωσμένων', 'mark_as_read' => 'Επισήμανση ως αναγνωσμένου', 'archive_all' => 'Αρχειοθέτηση όλων', 'no_notifications' => 'Δεν υπάρχουν ειδοποιήσεις', - 'support' => [ 'docs' => 'Τεκμηρίωση', 'referral' => 'Κερδίστε 30% από συστάσεις', diff --git a/lang/el/welcome.php b/lang/el/welcome.php index ae97a50c..a2801c25 100644 --- a/lang/el/welcome.php +++ b/lang/el/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Πώς μας βρήκατε;', 'referral_source_description' => 'Αυτό μας βοηθά να καταλάβουμε πώς οι άνθρωποι ανακαλύπτουν το TryPost.', 'referral_source' => [ - 'google' => 'Google ή αναζήτηση', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ή Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Βοηθός AI (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Κατάλογος (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Φίλος ή συνάδελφος', - 'blog' => 'Blog, newsletter ή άρθρο', + 'founder' => 'Ιδρυτής', + 'blog' => 'Blog / newsletter', 'other' => 'Κάτι άλλο', ], ]; diff --git a/lang/en/mcp.php b/lang/en/mcp.php index 9b1285f9..955f0c90 100644 --- a/lang/en/mcp.php +++ b/lang/en/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Copy your TryPost server URL', 'open_step' => 'Open your AI assistant', 'copy' => 'Copy URL', - 'connect' => 'Connect with :client', + 'connect' => 'Connect :client', 'step_add' => 'Paste the name, URL, or config below into your app. Sign-in opens in the browser the first time it connects.', 'name_label' => 'Name', 'url_label' => 'Server URL', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Authorization failed - TryPost', + 'error_title' => 'Could not connect', + 'error_body' => 'This authorization request is invalid or expired. Close this window and try connecting again from your MCP client.', + 'error_code' => 'Error: :error', ], 'other_clients_title' => 'Other apps', diff --git a/lang/en/onboarding.php b/lang/en/onboarding.php index 1c96007c..694edc9a 100644 --- a/lang/en/onboarding.php +++ b/lang/en/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Welcome to TryPost', - 'description' => 'Tell us what best describes you or your business so we can tailor your experience.', - 'continue' => 'Continue', - 'personas' => [ - 'creator' => 'Content creator', - 'freelancer' => 'Freelancer', - 'developer' => 'Developer', - 'startup' => 'Startup', - 'agency' => 'Agency', - 'small_business' => 'Small business', - 'marketer' => 'Marketer', - 'online_store' => 'Online store', - 'other' => 'Other', + 'title' => 'Getting started', + 'welcome' => 'Welcome to TryPost, :name', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', + 'skip_step' => 'Skip this step', + 'continue' => 'Continue to TryPost', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', + 'skipped' => 'Skipped', ], - 'goals_title' => 'What\'s your goal with TryPost?', - 'goals_description' => 'Pick everything that fits and we\'ll set TryPost up for you.', - 'goals' => [ - 'save_time' => 'Save time by posting everywhere at once', - 'ai_content' => 'Generate posts with TryPost AI', - 'use_mcp' => 'Create posts from Claude, ChatGPT, or Cursor', - 'plan_calendar' => 'Plan my posts on a calendar', - 'stay_on_brand' => 'Keep every post on brand', - 'grow_audience' => 'Grow my audience and engagement', - 'drive_sales' => 'Get more traffic and sales', - 'manage_clients' => 'Manage several brands or clients', - 'just_exploring' => 'Just exploring for now', - 'other' => 'Something else', + 'mcp' => [ + 'title' => 'Connect your AI assistant', + 'description' => 'Add TryPost as an MCP server so your assistant can create and manage social posts for you.', + 'copied' => 'MCP URL copied.', ], - 'referral_source_title' => 'How did you find us?', - 'referral_source_description' => 'This helps us understand how people discover TryPost.', - 'referral_source' => [ - 'google' => 'Google or search', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram or Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI assistant (ChatGPT, Claude…)', - 'friend' => 'Friend or colleague', - 'blog' => 'Blog, newsletter or article', - 'other' => 'Something else', + 'social' => [ + 'title' => 'Connect a social account', + 'description' => 'Choose at least one network where TryPost can publish your content.', + 'connected_elsewhere' => 'You already connected an account in another workspace, so this step is done.', ], - 'connect' => [ - 'title' => 'Connect your first network', - 'description' => 'Link at least one social account to start scheduling. You can add more anytime.', - 'must_connect' => 'Connect at least one network to continue.', + 'first_post' => [ + 'title' => 'Create your first post', + 'description' => 'Try this starter prompt with your connected assistant, or create the post directly in TryPost.', + 'prompt_label' => 'Sample prompt', + 'sample_prompt' => 'Create a friendly social post introducing my brand and adapt it for each connected network.', + 'copy_prompt' => 'Copy prompt', + 'copied' => 'Sample prompt copied.', + 'create_button' => 'Create your first post', + 'or' => 'or', + ], + 'ready' => [ + 'title' => 'You are ready to publish', + 'description' => 'You are set. Continue to TryPost and start planning your content.', ], ]; diff --git a/lang/en/sidebar.php b/lang/en/sidebar.php index d8d21536..2b4f5c4a 100644 --- a/lang/en/sidebar.php +++ b/lang/en/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Select workspace', 'create_workspace' => 'Create workspace', 'create_post' => 'Create post', - 'profile' => 'Profile', 'my_account' => 'My account', 'account_settings' => 'Account & billing', 'workspace_settings' => 'Workspace settings', 'log_out' => 'Log out', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Select', - - 'theme' => 'Theme: :name', - 'theme_light' => 'Light', - 'theme_dark' => 'Dark', - 'theme_system' => 'System', - + 'workspace' => [ + 'connections' => 'Connections', + 'signatures' => 'Signatures', + 'labels' => 'Labels', + 'assets' => 'Assets', + 'mcp' => 'MCP', + ], 'language' => 'Language: :name', - 'language_select' => 'Language: Select', - 'groups' => [ 'posts' => 'Posts', 'workspace' => 'Workspace', 'others' => 'Others', ], - 'analytics' => 'Analytics', 'automations' => 'Automations', - 'settings' => 'Settings', - + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Finish setup', 'posts' => [ 'calendar' => 'Calendar', 'all' => 'All', @@ -41,22 +35,11 @@ 'posted' => 'Posted', 'drafts' => 'Drafts', ], - - 'workspace' => [ - 'connections' => 'Connections', - 'signatures' => 'Signatures', - 'labels' => 'Labels', - 'assets' => 'Assets', - 'mcp' => 'MCP', - 'api_keys' => 'API Keys', - ], - 'notifications' => 'Notifications', 'mark_all_read' => 'Mark all as read', 'mark_as_read' => 'Mark as read', 'archive_all' => 'Archive all', 'no_notifications' => 'No notifications', - 'support' => [ 'docs' => 'Documentation', 'referral' => 'Earn 30% referral', diff --git a/lang/en/welcome.php b/lang/en/welcome.php index 262db0ce..d83a8e37 100644 --- a/lang/en/welcome.php +++ b/lang/en/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'How did you find us?', 'referral_source_description' => 'This helps us understand how people discover TryPost.', 'referral_source' => [ - 'google' => 'Google or search', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram or Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI assistant (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Directory (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Friend or colleague', - 'blog' => 'Blog, newsletter or article', + 'founder' => 'Founder', + 'blog' => 'Blog / newsletter', 'other' => 'Something else', ], ]; diff --git a/lang/es/mcp.php b/lang/es/mcp.php index d2988170..052f224c 100644 --- a/lang/es/mcp.php +++ b/lang/es/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Copia la URL del servidor TryPost', 'open_step' => 'Abre tu asistente de IA', 'copy' => 'Copiar URL', - 'connect' => 'Conectar con :client', + 'connect' => 'Conectar :client', 'step_add' => 'Pega el nombre, la URL o la config abajo en tu app. El inicio de sesión se abre en el navegador la primera vez.', 'name_label' => 'Nombre', 'url_label' => 'URL del servidor', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Error de autorización - TryPost', + 'error_title' => 'No se pudo conectar', + 'error_body' => 'Esta solicitud de autorización no es válida o ha caducado. Cierra esta ventana e intenta conectar de nuevo desde tu cliente MCP.', + 'error_code' => 'Error: :error', ], 'other_clients_title' => 'Otras apps', diff --git a/lang/es/onboarding.php b/lang/es/onboarding.php index 91e12023..844ba718 100644 --- a/lang/es/onboarding.php +++ b/lang/es/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Bienvenido a TryPost', - 'description' => 'Cuéntanos qué te describe mejor a ti o a tu negocio para personalizar tu experiencia.', - 'continue' => 'Continuar', - 'personas' => [ - 'creator' => 'Creador de contenido', - 'freelancer' => 'Freelancer', - 'developer' => 'Desarrollador', - 'startup' => 'Startup', - 'agency' => 'Agencia', - 'small_business' => 'Pequeña empresa', - 'marketer' => 'Profesional de marketing', - 'online_store' => 'Tienda online', - 'other' => 'Otro', + 'title' => 'Primeros pasos', + 'welcome' => 'Bienvenido a TryPost, :name', + 'description' => 'Sigue los pasos a continuación para ver cómo funciona TryPost y publicar tu primer post.', + 'skip_step' => 'Omitir este paso', + 'continue' => 'Continuar a TryPost', + 'status' => [ + 'complete' => 'Completado', + 'todo' => 'Pendiente', + 'skipped' => 'Omitido', ], - 'goals_title' => '¿Cuál es tu objetivo con TryPost?', - 'goals_description' => 'Marca todo lo que encaje y adaptamos TryPost a ti.', - 'goals' => [ - 'save_time' => 'Ahorrar tiempo publicando en todas mis redes a la vez', - 'ai_content' => 'Generar publicaciones con la IA de TryPost', - 'use_mcp' => 'Crear publicaciones desde Claude, ChatGPT o Cursor', - 'plan_calendar' => 'Planificar mis publicaciones en un calendario', - 'stay_on_brand' => 'Mantener la coherencia de mi marca', - 'grow_audience' => 'Hacer crecer mi audiencia y engagement', - 'drive_sales' => 'Conseguir más tráfico y ventas', - 'manage_clients' => 'Gestionar varias marcas o clientes', - 'just_exploring' => 'Solo estoy explorando por ahora', - 'other' => 'Otra cosa', + 'mcp' => [ + 'title' => 'Conecta tu asistente de IA', + 'description' => 'Añade TryPost como servidor MCP para que tu asistente pueda crear y gestionar posts por ti.', + 'copied' => 'URL de MCP copiada.', ], - 'referral_source_title' => '¿Cómo nos encontraste?', - 'referral_source_description' => 'Esto nos ayuda a entender cómo la gente descubre TryPost.', - 'referral_source' => [ - 'google' => 'Google o búsqueda', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram o Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Asistente de IA (ChatGPT, Claude…)', - 'friend' => 'Amigo o colega', - 'blog' => 'Blog, newsletter o artículo', - 'other' => 'Otra cosa', + 'social' => [ + 'title' => 'Conecta una cuenta social', + 'description' => 'Elige al menos una red donde TryPost pueda publicar tu contenido.', + 'connected_elsewhere' => 'Ya conectaste una cuenta en otro workspace, así que este paso está listo.', ], - 'connect' => [ - 'title' => 'Conecta tu primera red', - 'description' => 'Vincula al menos una cuenta social para empezar a programar. Puedes añadir más cuando quieras.', - 'must_connect' => 'Conecta al menos una red para continuar.', + 'first_post' => [ + 'title' => 'Crea tu primer post', + 'description' => 'Prueba este prompt inicial con tu asistente conectado, o crea el post directamente en TryPost.', + 'prompt_label' => 'Prompt de ejemplo', + 'sample_prompt' => 'Crea un post social amable presentando mi marca y adáptalo para cada red conectada.', + 'copy_prompt' => 'Copiar prompt', + 'copied' => 'Prompt de ejemplo copiado.', + 'create_button' => 'Crear tu primer post', + 'or' => 'o', + ], + 'ready' => [ + 'title' => 'Listo para publicar', + 'description' => 'Todo listo. Continúa a TryPost y empieza a planificar tu contenido.', ], ]; diff --git a/lang/es/sidebar.php b/lang/es/sidebar.php index 63b454b6..aeb9b33d 100644 --- a/lang/es/sidebar.php +++ b/lang/es/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Seleccionar workspace', 'create_workspace' => 'Crear workspace', 'create_post' => 'Crear post', - 'profile' => 'Perfil', 'my_account' => 'Mi cuenta', 'account_settings' => 'Cuenta y facturación', 'workspace_settings' => 'Configuración del workspace', 'log_out' => 'Cerrar sesión', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Seleccionar', - - 'theme' => 'Tema: :name', - 'theme_light' => 'Claro', - 'theme_dark' => 'Oscuro', - 'theme_system' => 'Sistema', - + 'workspace' => [ + 'connections' => 'Conexiones', + 'signatures' => 'Firmas', + 'labels' => 'Etiquetas', + 'assets' => 'Medios', + 'mcp' => 'MCP', + ], 'language' => 'Idioma: :name', - 'language_select' => 'Idioma: Seleccionar', - 'groups' => [ 'posts' => 'Posts', 'workspace' => 'Workspace', 'others' => 'Otros', ], - 'analytics' => 'Analytics', 'automations' => 'Automatizaciones', - 'settings' => 'Configuración', - + 'onboarding' => 'Primeros pasos', + 'onboarding_hint' => 'Termina la configuración', 'posts' => [ 'calendar' => 'Calendario', 'all' => 'Todos', @@ -41,22 +35,11 @@ 'posted' => 'Publicados', 'drafts' => 'Borradores', ], - - 'workspace' => [ - 'connections' => 'Conexiones', - 'signatures' => 'Firmas', - 'labels' => 'Etiquetas', - 'assets' => 'Medios', - 'mcp' => 'MCP', - 'api_keys' => 'API Keys', - ], - 'notifications' => 'Notificaciones', 'mark_all_read' => 'Marcar todo como leído', 'mark_as_read' => 'Marcar como leído', 'archive_all' => 'Archivar todo', 'no_notifications' => 'Sin notificaciones', - 'support' => [ 'docs' => 'Documentación', 'referral' => 'Gana 30% de comisión', diff --git a/lang/es/welcome.php b/lang/es/welcome.php index 17c5bf55..be2ee737 100644 --- a/lang/es/welcome.php +++ b/lang/es/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => '¿Cómo nos encontraste?', 'referral_source_description' => 'Esto nos ayuda a entender cómo la gente descubre TryPost.', 'referral_source' => [ - 'google' => 'Google o búsqueda', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram o Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Asistente de IA (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Directorio (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Amigo o colega', - 'blog' => 'Blog, newsletter o artículo', + 'founder' => 'Fundador', + 'blog' => 'Blog / newsletter', 'other' => 'Otra cosa', ], ]; diff --git a/lang/fr/mcp.php b/lang/fr/mcp.php index ea927840..6fd13692 100644 --- a/lang/fr/mcp.php +++ b/lang/fr/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Copiez l’URL du serveur TryPost', 'open_step' => 'Ouvrez votre assistant IA', 'copy' => 'Copier l’URL', - 'connect' => 'Connecter avec :client', + 'connect' => 'Connecter :client', 'step_add' => 'Collez le nom, l’URL ou la config ci-dessous dans votre app. La connexion s’ouvre dans le navigateur la première fois.', 'name_label' => 'Nom', 'url_label' => 'URL du serveur', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Échec de l\'autorisation - TryPost', + 'error_title' => 'Impossible de se connecter', + 'error_body' => 'Cette demande d\'autorisation est invalide ou a expiré. Fermez cette fenêtre et réessayez depuis votre client MCP.', + 'error_code' => 'Erreur : :error', ], 'other_clients_title' => 'Autres apps', diff --git a/lang/fr/onboarding.php b/lang/fr/onboarding.php index 4c532e19..1455dcdc 100644 --- a/lang/fr/onboarding.php +++ b/lang/fr/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Bienvenue sur TryPost', - 'description' => 'Dites-nous ce qui vous décrit le mieux, vous ou votre entreprise, afin que nous puissions personnaliser votre expérience.', - 'continue' => 'Continuer', - 'personas' => [ - 'creator' => 'Créateur de contenu', - 'freelancer' => 'Freelance', - 'developer' => 'Développeur', - 'startup' => 'Startup', - 'agency' => 'Agence', - 'small_business' => 'Petite entreprise', - 'marketer' => 'Marketeur', - 'online_store' => 'Boutique en ligne', - 'other' => 'Autre', + 'title' => 'Premiers pas', + 'welcome' => 'Bienvenue sur TryPost, :name', + 'description' => 'Suivez les étapes ci-dessous pour découvrir comment TryPost fonctionne et publier votre premier post.', + 'skip_step' => 'Ignorer cette étape', + 'continue' => 'Continuer vers TryPost', + 'status' => [ + 'complete' => 'Terminé', + 'todo' => 'À faire', + 'skipped' => 'Ignorée', ], - 'goals_title' => 'Quel est votre objectif avec TryPost ?', - 'goals_description' => 'Choisissez tout ce qui vous correspond et nous configurerons TryPost pour vous.', - 'goals' => [ - 'save_time' => 'Gagner du temps en publiant partout à la fois', - 'ai_content' => 'Générer des publications avec l\'IA TryPost', - 'use_mcp' => 'Créer des publications depuis Claude, ChatGPT ou Cursor', - 'plan_calendar' => 'Planifier mes publications sur un calendrier', - 'stay_on_brand' => 'Garder chaque publication fidèle à ma marque', - 'grow_audience' => 'Développer mon audience et mon engagement', - 'drive_sales' => 'Obtenir plus de trafic et de ventes', - 'manage_clients' => 'Gérer plusieurs marques ou clients', - 'just_exploring' => 'Je découvre pour l\'instant', - 'other' => 'Autre chose', + 'mcp' => [ + 'title' => 'Connectez votre assistant IA', + 'description' => 'Ajoutez TryPost comme serveur MCP pour que votre assistant puisse créer et gérer vos posts sociaux.', + 'copied' => 'URL MCP copiée.', ], - 'referral_source_title' => 'Comment nous avez-vous connus ?', - 'referral_source_description' => 'Cela nous aide à comprendre comment les gens découvrent TryPost.', - 'referral_source' => [ - 'google' => 'Google ou recherche', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ou Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistant IA (ChatGPT, Claude…)', - 'friend' => 'Ami ou collègue', - 'blog' => 'Blog, newsletter ou article', - 'other' => 'Autre chose', + 'social' => [ + 'title' => 'Connectez un compte social', + 'description' => 'Choisissez au moins un réseau où TryPost pourra publier votre contenu.', + 'connected_elsewhere' => 'Vous avez déjà connecté un compte dans un autre workspace, cette étape est donc terminée.', ], - 'connect' => [ - 'title' => 'Connectez votre premier réseau', - 'description' => 'Associez au moins un compte social pour commencer à programmer. Vous pourrez en ajouter d\'autres à tout moment.', - 'must_connect' => 'Connectez au moins un réseau pour continuer.', + 'first_post' => [ + 'title' => 'Créez votre premier post', + 'description' => 'Essayez ce prompt de démarrage avec votre assistant connecté, ou créez le post directement dans TryPost.', + 'prompt_label' => 'Prompt d’exemple', + 'sample_prompt' => 'Crée un post social amical présentant ma marque et adapte-le pour chaque réseau connecté.', + 'copy_prompt' => 'Copier le prompt', + 'copied' => 'Prompt d’exemple copié.', + 'create_button' => 'Créer votre premier post', + 'or' => 'ou', + ], + 'ready' => [ + 'title' => 'Vous êtes prêt à publier', + 'description' => 'Tout est bon. Continuez vers TryPost et commencez à planifier votre contenu.', ], ]; diff --git a/lang/fr/sidebar.php b/lang/fr/sidebar.php index b5f4d9b5..14208543 100644 --- a/lang/fr/sidebar.php +++ b/lang/fr/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Sélectionner un espace de travail', 'create_workspace' => 'Créer un espace de travail', 'create_post' => 'Créer une publication', - 'profile' => 'Profil', 'my_account' => 'Mon compte', 'account_settings' => 'Compte et facturation', 'workspace_settings' => 'Paramètres de l\'espace de travail', 'log_out' => 'Se déconnecter', - - 'workspace' => 'Espace de travail : :name', - 'workspace_select' => 'Espace de travail : Sélectionner', - - 'theme' => 'Thème : :name', - 'theme_light' => 'Clair', - 'theme_dark' => 'Sombre', - 'theme_system' => 'Système', - + 'workspace' => [ + 'connections' => 'Connexions', + 'signatures' => 'Signatures', + 'labels' => 'Étiquettes', + 'assets' => 'Médias', + 'mcp' => 'MCP', + ], 'language' => 'Langue : :name', - 'language_select' => 'Langue : Sélectionner', - 'groups' => [ 'posts' => 'Publications', 'workspace' => 'Espace de travail', 'others' => 'Autres', ], - 'analytics' => 'Statistiques', 'automations' => 'Automatisations', - 'settings' => 'Paramètres', - + 'onboarding' => 'Premiers pas', + 'onboarding_hint' => 'Terminer la configuration', 'posts' => [ 'calendar' => 'Calendrier', 'all' => 'Toutes', @@ -41,22 +35,11 @@ 'posted' => 'Publiées', 'drafts' => 'Brouillons', ], - - 'workspace' => [ - 'connections' => 'Connexions', - 'signatures' => 'Signatures', - 'labels' => 'Étiquettes', - 'assets' => 'Médias', - 'mcp' => 'MCP', - 'api_keys' => 'Clés API', - ], - 'notifications' => 'Notifications', 'mark_all_read' => 'Tout marquer comme lu', 'mark_as_read' => 'Marquer comme lu', 'archive_all' => 'Tout archiver', 'no_notifications' => 'Aucune notification', - 'support' => [ 'docs' => 'Documentation', 'referral' => 'Gagnez 30 % de parrainage', diff --git a/lang/fr/welcome.php b/lang/fr/welcome.php index 9eef5802..f7648989 100644 --- a/lang/fr/welcome.php +++ b/lang/fr/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Comment nous avez-vous connus ?', 'referral_source_description' => 'Cela nous aide à comprendre comment les gens découvrent TryPost.', 'referral_source' => [ - 'google' => 'Google ou recherche', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ou Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistant IA (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Annuaire (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Ami ou collègue', - 'blog' => 'Blog, newsletter ou article', + 'founder' => 'Fondateur', + 'blog' => 'Blog / newsletter', 'other' => 'Autre chose', ], ]; diff --git a/lang/it/mcp.php b/lang/it/mcp.php index b4385ce0..c61e27de 100644 --- a/lang/it/mcp.php +++ b/lang/it/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Copia l’URL del server TryPost', 'open_step' => 'Apri il tuo assistente IA', 'copy' => 'Copia URL', - 'connect' => 'Collega con :client', + 'connect' => 'Collega :client', 'step_add' => 'Incolla nome, URL o config qui sotto nella tua app. Il login si apre nel browser al primo collegamento.', 'name_label' => 'Nome', 'url_label' => 'URL del server', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Autorizzazione non riuscita - TryPost', + 'error_title' => 'Impossibile connettersi', + 'error_body' => 'Questa richiesta di autorizzazione non è valida o è scaduta. Chiudi questa finestra e riprova dal tuo client MCP.', + 'error_code' => 'Errore: :error', ], 'other_clients_title' => 'Altre app', diff --git a/lang/it/onboarding.php b/lang/it/onboarding.php index a425efd5..ac667df8 100644 --- a/lang/it/onboarding.php +++ b/lang/it/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Benvenuto su TryPost', - 'description' => 'Dicci cosa descrive meglio te o la tua attività così possiamo personalizzare la tua esperienza.', - 'continue' => 'Continua', - 'personas' => [ - 'creator' => 'Creatore di contenuti', - 'freelancer' => 'Freelance', - 'developer' => 'Sviluppatore', - 'startup' => 'Startup', - 'agency' => 'Agenzia', - 'small_business' => 'Piccola impresa', - 'marketer' => 'Marketer', - 'online_store' => 'Negozio online', - 'other' => 'Altro', + 'title' => 'Primi passi', + 'welcome' => 'Benvenuto su TryPost, :name', + 'description' => 'Segui i passaggi qui sotto per vedere come funziona TryPost e pubblicare il tuo primo post.', + 'skip_step' => 'Salta questo passaggio', + 'continue' => 'Continua su TryPost', + 'status' => [ + 'complete' => 'Completato', + 'todo' => 'Da fare', + 'skipped' => 'Saltato', ], - 'goals_title' => 'Qual è il tuo obiettivo con TryPost?', - 'goals_description' => 'Scegli tutto ciò che fa per te e configureremo TryPost per te.', - 'goals' => [ - 'save_time' => 'Risparmiare tempo pubblicando ovunque in una volta', - 'ai_content' => 'Generare post con l\'IA di TryPost', - 'use_mcp' => 'Creare post da Claude, ChatGPT o Cursor', - 'plan_calendar' => 'Pianificare i miei post su un calendario', - 'stay_on_brand' => 'Mantenere ogni post in linea con il brand', - 'grow_audience' => 'Far crescere il mio pubblico e il coinvolgimento', - 'drive_sales' => 'Ottenere più traffico e vendite', - 'manage_clients' => 'Gestire più brand o clienti', - 'just_exploring' => 'Sto solo dando un\'occhiata', - 'other' => 'Qualcos\'altro', + 'mcp' => [ + 'title' => 'Collega il tuo assistente IA', + 'description' => 'Aggiungi TryPost come server MCP così il tuo assistente può creare e gestire i post social per te.', + 'copied' => 'URL MCP copiato.', ], - 'referral_source_title' => 'Come ci hai trovato?', - 'referral_source_description' => 'Questo ci aiuta a capire come le persone scoprono TryPost.', - 'referral_source' => [ - 'google' => 'Google o ricerca', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram o Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistente IA (ChatGPT, Claude…)', - 'friend' => 'Amico o collega', - 'blog' => 'Blog, newsletter o articolo', - 'other' => 'Qualcos\'altro', + 'social' => [ + 'title' => 'Collega un account social', + 'description' => 'Scegli almeno una rete dove TryPost possa pubblicare i tuoi contenuti.', + 'connected_elsewhere' => 'Hai già collegato un account in un altro workspace, quindi questo passaggio è completato.', ], - 'connect' => [ - 'title' => 'Collega la tua prima rete', - 'description' => 'Collega almeno un account social per iniziare a programmare. Puoi aggiungerne altri in qualsiasi momento.', - 'must_connect' => 'Collega almeno una rete per continuare.', + 'first_post' => [ + 'title' => 'Crea il tuo primo post', + 'description' => 'Prova questo prompt iniziale con il tuo assistente collegato, oppure crea il post direttamente in TryPost.', + 'prompt_label' => 'Prompt di esempio', + 'sample_prompt' => 'Crea un post social amichevole per presentare il mio brand e adattalo a ogni rete collegata.', + 'copy_prompt' => 'Copia prompt', + 'copied' => 'Prompt di esempio copiato.', + 'create_button' => 'Crea il tuo primo post', + 'or' => 'oppure', + ], + 'ready' => [ + 'title' => 'Sei pronto a pubblicare', + 'description' => 'Tutto a posto. Continua su TryPost e inizia a pianificare i tuoi contenuti.', ], ]; diff --git a/lang/it/sidebar.php b/lang/it/sidebar.php index 9b7c43e4..61a15fc6 100644 --- a/lang/it/sidebar.php +++ b/lang/it/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Seleziona workspace', 'create_workspace' => 'Crea workspace', 'create_post' => 'Crea post', - 'profile' => 'Profilo', 'my_account' => 'Il mio account', 'account_settings' => 'Account e fatturazione', 'workspace_settings' => 'Impostazioni workspace', 'log_out' => 'Esci', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Seleziona', - - 'theme' => 'Tema: :name', - 'theme_light' => 'Chiaro', - 'theme_dark' => 'Scuro', - 'theme_system' => 'Sistema', - + 'workspace' => [ + 'connections' => 'Connessioni', + 'signatures' => 'Firme', + 'labels' => 'Etichette', + 'assets' => 'Risorse', + 'mcp' => 'MCP', + ], 'language' => 'Lingua: :name', - 'language_select' => 'Lingua: Seleziona', - 'groups' => [ 'posts' => 'Post', 'workspace' => 'Workspace', 'others' => 'Altro', ], - 'analytics' => 'Statistiche', 'automations' => 'Automazioni', - 'settings' => 'Impostazioni', - + 'onboarding' => 'Primi passi', + 'onboarding_hint' => 'Completa la configurazione', 'posts' => [ 'calendar' => 'Calendario', 'all' => 'Tutti', @@ -41,22 +35,11 @@ 'posted' => 'Pubblicati', 'drafts' => 'Bozze', ], - - 'workspace' => [ - 'connections' => 'Connessioni', - 'signatures' => 'Firme', - 'labels' => 'Etichette', - 'assets' => 'Risorse', - 'mcp' => 'MCP', - 'api_keys' => 'Chiavi API', - ], - 'notifications' => 'Notifiche', 'mark_all_read' => 'Segna tutte come lette', 'mark_as_read' => 'Segna come letta', 'archive_all' => 'Archivia tutte', 'no_notifications' => 'Nessuna notifica', - 'support' => [ 'docs' => 'Documentazione', 'referral' => 'Guadagna il 30% di referral', diff --git a/lang/it/welcome.php b/lang/it/welcome.php index 9e32b27a..e6f2667a 100644 --- a/lang/it/welcome.php +++ b/lang/it/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Come ci hai trovato?', 'referral_source_description' => 'Questo ci aiuta a capire come le persone scoprono TryPost.', 'referral_source' => [ - 'google' => 'Google o ricerca', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram o Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistente IA (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Directory (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Amico o collega', - 'blog' => 'Blog, newsletter o articolo', + 'founder' => 'Fondatore', + 'blog' => 'Blog / newsletter', 'other' => 'Qualcos\'altro', ], ]; diff --git a/lang/ja/mcp.php b/lang/ja/mcp.php index 3869e1bc..189459d9 100644 --- a/lang/ja/mcp.php +++ b/lang/ja/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'TryPostサーバーURLをコピー', 'open_step' => 'AIアシスタントを開く', 'copy' => 'URLをコピー', - 'connect' => ':clientで接続', + 'connect' => ':clientを接続', 'step_add' => '下の名前・URL・設定をアプリに貼り付けてください。初回接続時はブラウザでログインが開きます。', 'name_label' => '名前', 'url_label' => 'サーバーURL', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => '認可に失敗しました - TryPost', + 'error_title' => '接続できませんでした', + 'error_body' => 'この認可リクエストは無効か期限切れです。このウィンドウを閉じ、MCPクライアントからもう一度接続してください。', + 'error_code' => 'エラー: :error', ], 'other_clients_title' => 'その他のアプリ', diff --git a/lang/ja/onboarding.php b/lang/ja/onboarding.php index d84992ce..4ed5065e 100644 --- a/lang/ja/onboarding.php +++ b/lang/ja/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'TryPost へようこそ', - 'description' => 'あなたやあなたのビジネスに最も当てはまるものを教えてください。体験を最適化します。', - 'continue' => '続ける', - 'personas' => [ - 'creator' => 'コンテンツクリエイター', - 'freelancer' => 'フリーランス', - 'developer' => '開発者', - 'startup' => 'スタートアップ', - 'agency' => '代理店', - 'small_business' => '中小企業', - 'marketer' => 'マーケター', - 'online_store' => 'オンラインストア', - 'other' => 'その他', + 'title' => 'はじめに', + 'welcome' => 'TryPostへようこそ、:nameさん', + 'description' => '以下のステップでTryPostの使い方を確認し、最初の投稿を公開しましょう。', + 'skip_step' => 'このステップをスキップ', + 'continue' => 'TryPostへ進む', + 'status' => [ + 'complete' => '完了', + 'todo' => '未完了', + 'skipped' => 'スキップ済み', ], - 'goals_title' => 'TryPost での目標は何ですか?', - 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', - 'goals' => [ - 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', - 'ai_content' => 'TryPost AI で投稿を生成する', - 'use_mcp' => 'Claude・ChatGPT・Cursor から投稿を作成する', - 'plan_calendar' => 'カレンダーで投稿を計画する', - 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', - 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', - 'drive_sales' => 'トラフィックと売上を増やす', - 'manage_clients' => '複数のブランドやクライアントを管理する', - 'just_exploring' => '今はまだ様子を見ている', - 'other' => 'その他', + 'mcp' => [ + 'title' => 'AIアシスタントを接続', + 'description' => 'TryPostをMCPサーバーとして追加すると、アシスタントがSNS投稿の作成・管理を行えます。', + 'copied' => 'MCP URLをコピーしました。', ], - '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' => 'その他', + 'social' => [ + 'title' => 'SNSアカウントを接続', + 'description' => 'TryPostが投稿できるネットワークを少なくとも1つ選びます。', + 'connected_elsewhere' => '別のワークスペースでアカウントを接続済みなので、このステップは完了しています。', ], - 'connect' => [ - 'title' => '最初のネットワークを接続', - 'description' => 'スケジュールを始めるには、少なくとも 1 つのソーシャルアカウントを連携してください。後からいつでも追加できます。', - 'must_connect' => '続けるには、少なくとも 1 つのネットワークを接続してください。', + 'first_post' => [ + 'title' => '最初の投稿を作成', + 'description' => '接続したアシスタントでこのスタータープロンプトを試すか、TryPostで直接投稿を作成します。', + 'prompt_label' => 'サンプルプロンプト', + 'sample_prompt' => 'ブランドを紹介する親しみやすいSNS投稿を作成し、接続済みの各ネットワーク向けに最適化してください。', + 'copy_prompt' => 'プロンプトをコピー', + 'copied' => 'サンプルプロンプトをコピーしました。', + 'create_button' => '最初の投稿を作成', + 'or' => 'または', + ], + 'ready' => [ + 'title' => '公開の準備ができました', + 'description' => '設定完了です。TryPostへ進み、コンテンツの計画を始めましょう。', ], ]; diff --git a/lang/ja/sidebar.php b/lang/ja/sidebar.php index 95f28854..8f5febe5 100644 --- a/lang/ja/sidebar.php +++ b/lang/ja/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'ワークスペースを選択', 'create_workspace' => 'ワークスペースを作成', 'create_post' => '投稿を作成', - 'profile' => 'プロフィール', 'my_account' => 'マイアカウント', 'account_settings' => 'アカウントと請求', 'workspace_settings' => 'ワークスペース設定', 'log_out' => 'ログアウト', - - 'workspace' => 'ワークスペース: :name', - 'workspace_select' => 'ワークスペース: 選択', - - 'theme' => 'テーマ: :name', - 'theme_light' => 'ライト', - 'theme_dark' => 'ダーク', - 'theme_system' => 'システム', - + 'workspace' => [ + 'connections' => '接続', + 'signatures' => '署名', + 'labels' => 'ラベル', + 'assets' => 'アセット', + 'mcp' => 'MCP', + ], 'language' => '言語: :name', - 'language_select' => '言語: 選択', - 'groups' => [ 'posts' => '投稿', 'workspace' => 'ワークスペース', 'others' => 'その他', ], - 'analytics' => 'アナリティクス', 'automations' => 'オートメーション', - 'settings' => '設定', - + 'onboarding' => 'はじめに', + 'onboarding_hint' => 'セットアップを完了', 'posts' => [ 'calendar' => 'カレンダー', 'all' => 'すべて', @@ -41,22 +35,11 @@ 'posted' => '公開済み', 'drafts' => '下書き', ], - - 'workspace' => [ - 'connections' => '接続', - 'signatures' => '署名', - 'labels' => 'ラベル', - 'assets' => 'アセット', - 'mcp' => 'MCP', - 'api_keys' => 'API キー', - ], - 'notifications' => '通知', 'mark_all_read' => 'すべて既読にする', 'mark_as_read' => '既読にする', 'archive_all' => 'すべてアーカイブ', 'no_notifications' => '通知はありません', - 'support' => [ 'docs' => 'ドキュメント', 'referral' => '30% の紹介報酬を獲得', diff --git a/lang/ja/welcome.php b/lang/ja/welcome.php index dcc87b1f..8507a5d4 100644 --- a/lang/ja/welcome.php +++ b/lang/ja/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'どこで私たちを知りましたか?', 'referral_source_description' => 'これは、人々がどのように TryPost を見つけるかを理解するのに役立ちます。', 'referral_source' => [ - 'google' => 'Google または検索', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram または Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI アシスタント(ChatGPT、Claude など)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'ディレクトリ(G2、Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => '友人または同僚', - 'blog' => 'ブログ、ニュースレター、記事', + 'founder' => '創業者', + 'blog' => 'Blog / newsletter', 'other' => 'その他', ], ]; diff --git a/lang/ko/mcp.php b/lang/ko/mcp.php index 7374be93..5f2b9347 100644 --- a/lang/ko/mcp.php +++ b/lang/ko/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'TryPost 서버 URL 복사', 'open_step' => 'AI 어시스턴트 열기', 'copy' => 'URL 복사', - 'connect' => ':client로 연결', + 'connect' => ':client 연결', 'step_add' => '아래 이름, URL 또는 설정을 앱에 붙여넣으세요. 처음 연결할 때 브라우저에서 로그인이 열립니다.', 'name_label' => '이름', 'url_label' => '서버 URL', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => '승인 실패 - TryPost', + 'error_title' => '연결할 수 없음', + 'error_body' => '이 승인 요청이 잘못되었거나 만료되었습니다. 이 창을 닫고 MCP 클라이언트에서 다시 연결하세요.', + 'error_code' => '오류: :error', ], 'other_clients_title' => '다른 앱', diff --git a/lang/ko/onboarding.php b/lang/ko/onboarding.php index 8a02c90b..f9fb2400 100644 --- a/lang/ko/onboarding.php +++ b/lang/ko/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'TryPost에 오신 것을 환영합니다', - 'description' => '회원님이나 비즈니스를 가장 잘 설명하는 항목을 알려주시면 맞춤형 경험을 제공해 드립니다.', - 'continue' => '계속', - 'personas' => [ - 'creator' => '콘텐츠 크리에이터', - 'freelancer' => '프리랜서', - 'developer' => '개발자', - 'startup' => '스타트업', - 'agency' => '에이전시', - 'small_business' => '소상공인', - 'marketer' => '마케터', - 'online_store' => '온라인 스토어', - 'other' => '기타', + 'title' => '시작하기', + 'welcome' => 'TryPost에 오신 걸 환영해요, :name', + 'description' => '아래 단계를 따라 TryPost 사용법을 확인하고 첫 게시물을 발행하세요.', + 'skip_step' => '이 단계 건너뛰기', + 'continue' => 'TryPost로 계속', + 'status' => [ + 'complete' => '완료', + 'todo' => '할 일', + 'skipped' => '건너뜀', ], - 'goals_title' => 'TryPost로 이루려는 목표는 무엇인가요?', - 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', - 'goals' => [ - 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', - 'ai_content' => 'TryPost AI로 게시물 생성', - 'use_mcp' => 'Claude, ChatGPT 또는 Cursor에서 게시물 작성', - 'plan_calendar' => '캘린더에서 게시물 계획', - 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', - 'grow_audience' => '팔로워와 참여 늘리기', - 'drive_sales' => '더 많은 트래픽과 판매 유도', - 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', - 'just_exploring' => '지금은 둘러보는 중', - 'other' => '다른 것', + 'mcp' => [ + 'title' => 'AI 어시스턴트 연결', + 'description' => 'TryPost를 MCP 서버로 추가하면 어시스턴트가 소셜 게시물을 만들고 관리할 수 있어요.', + 'copied' => 'MCP URL이 복사되었어요.', ], - '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' => '기타', + 'social' => [ + 'title' => '소셜 계정 연결', + 'description' => 'TryPost가 콘텐츠를 발행할 네트워크를 하나 이상 선택하세요.', + 'connected_elsewhere' => '다른 워크스페이스에서 이미 계정을 연결했으므로 이 단계는 완료되었습니다.', ], - 'connect' => [ - 'title' => '첫 네트워크 연결', - 'description' => '예약을 시작하려면 소셜 계정을 하나 이상 연결하세요. 언제든지 추가할 수 있습니다.', - 'must_connect' => '계속하려면 네트워크를 하나 이상 연결하세요.', + 'first_post' => [ + 'title' => '첫 게시물 만들기', + 'description' => '연결된 어시스턴트에서 이 시작 프롬프트를 써 보거나, TryPost에서 바로 게시물을 만드세요.', + 'prompt_label' => '샘플 프롬프트', + 'sample_prompt' => '내 브랜드를 소개하는 친근한 소셜 게시물을 만들고 연결된 각 네트워크에 맞게 조정해 주세요.', + 'copy_prompt' => '프롬프트 복사', + 'copied' => '샘플 프롬프트가 복사되었어요.', + 'create_button' => '첫 게시물 만들기', + 'or' => '또는', + ], + 'ready' => [ + 'title' => '발행할 준비가 됐어요', + 'description' => '설정이 끝났어요. TryPost로 가서 콘텐츠 계획을 시작하세요.', ], ]; diff --git a/lang/ko/sidebar.php b/lang/ko/sidebar.php index 305badd9..ddc3c309 100644 --- a/lang/ko/sidebar.php +++ b/lang/ko/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => '워크스페이스 선택', 'create_workspace' => '워크스페이스 만들기', 'create_post' => '게시물 만들기', - 'profile' => '프로필', 'my_account' => '내 계정', 'account_settings' => '계정 및 결제', 'workspace_settings' => '워크스페이스 설정', 'log_out' => '로그아웃', - - 'workspace' => '워크스페이스: :name', - 'workspace_select' => '워크스페이스: 선택', - - 'theme' => '테마: :name', - 'theme_light' => '라이트', - 'theme_dark' => '다크', - 'theme_system' => '시스템', - + 'workspace' => [ + 'connections' => '연결', + 'signatures' => '서명', + 'labels' => '라벨', + 'assets' => '에셋', + 'mcp' => 'MCP', + ], 'language' => '언어: :name', - 'language_select' => '언어: 선택', - 'groups' => [ 'posts' => '게시물', 'workspace' => '워크스페이스', 'others' => '기타', ], - 'analytics' => '분석', 'automations' => '자동화', - 'settings' => '설정', - + 'onboarding' => '시작하기', + 'onboarding_hint' => '설정 마치기', 'posts' => [ 'calendar' => '캘린더', 'all' => '전체', @@ -41,22 +35,11 @@ 'posted' => '게시됨', 'drafts' => '초안', ], - - 'workspace' => [ - 'connections' => '연결', - 'signatures' => '서명', - 'labels' => '라벨', - 'assets' => '에셋', - 'mcp' => 'MCP', - 'api_keys' => 'API 키', - ], - 'notifications' => '알림', 'mark_all_read' => '모두 읽음으로 표시', 'mark_as_read' => '읽음으로 표시', 'archive_all' => '모두 보관', 'no_notifications' => '알림 없음', - 'support' => [ 'docs' => '문서', 'referral' => '30% 추천 수익 받기', diff --git a/lang/ko/welcome.php b/lang/ko/welcome.php index 215f96f4..15a08121 100644 --- a/lang/ko/welcome.php +++ b/lang/ko/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => '저희를 어떻게 알게 되셨나요?', 'referral_source_description' => '사람들이 TryPost를 어떻게 발견하는지 파악하는 데 도움이 됩니다.', 'referral_source' => [ - 'google' => 'Google 또는 검색', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram 또는 Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI 어시스턴트 (ChatGPT, Claude 등)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => '디렉터리 (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => '친구 또는 동료', - 'blog' => '블로그, 뉴스레터 또는 기사', + 'founder' => '창업자', + 'blog' => 'Blog / newsletter', 'other' => '기타', ], ]; diff --git a/lang/nl/mcp.php b/lang/nl/mcp.php index 80c14153..71f4f026 100644 --- a/lang/nl/mcp.php +++ b/lang/nl/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Kopieer je TryPost-server-URL', 'open_step' => 'Open je AI-assistent', 'copy' => 'URL kopiëren', - 'connect' => 'Verbinden met :client', + 'connect' => 'Verbind :client', 'step_add' => 'Plak de naam, URL of config hieronder in je app. Inloggen opent in de browser bij de eerste verbinding.', 'name_label' => 'Naam', 'url_label' => 'Server-URL', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Autorisatie mislukt - TryPost', + 'error_title' => 'Verbinden mislukt', + 'error_body' => 'Dit autorisatieverzoek is ongeldig of verlopen. Sluit dit venster en probeer opnieuw te verbinden via je MCP-client.', + 'error_code' => 'Fout: :error', ], 'other_clients_title' => 'Andere apps', diff --git a/lang/nl/onboarding.php b/lang/nl/onboarding.php index 88367ef4..025029e4 100644 --- a/lang/nl/onboarding.php +++ b/lang/nl/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Welkom bij TryPost', - 'description' => 'Vertel ons wat jou of je bedrijf het beste omschrijft, zodat we je ervaring kunnen afstemmen.', - 'continue' => 'Doorgaan', - 'personas' => [ - 'creator' => 'Contentmaker', - 'freelancer' => 'Freelancer', - 'developer' => 'Ontwikkelaar', - 'startup' => 'Startup', - 'agency' => 'Bureau', - 'small_business' => 'Klein bedrijf', - 'marketer' => 'Marketeer', - 'online_store' => 'Webshop', - 'other' => 'Anders', + 'title' => 'Aan de slag', + 'welcome' => 'Welkom bij TryPost, :name', + 'description' => 'Volg de stappen hieronder om te zien hoe TryPost werkt en je eerste post te publiceren.', + 'skip_step' => 'Deze stap overslaan', + 'continue' => 'Doorgaan naar TryPost', + 'status' => [ + 'complete' => 'Voltooid', + 'todo' => 'Te doen', + 'skipped' => 'Overgeslagen', ], - 'goals_title' => 'Wat is je doel met TryPost?', - 'goals_description' => 'Kies alles wat past en we stellen TryPost voor je in.', - 'goals' => [ - 'save_time' => 'Tijd besparen door overal tegelijk te posten', - 'ai_content' => 'Posts genereren met TryPost AI', - 'use_mcp' => 'Posts maken via Claude, ChatGPT of Cursor', - 'plan_calendar' => 'Mijn posts plannen op een kalender', - 'stay_on_brand' => 'Elke post in lijn met mijn merk houden', - 'grow_audience' => 'Mijn publiek en betrokkenheid laten groeien', - 'drive_sales' => 'Meer verkeer en verkopen krijgen', - 'manage_clients' => 'Meerdere merken of klanten beheren', - 'just_exploring' => 'Voorlopig gewoon aan het verkennen', - 'other' => 'Iets anders', + 'mcp' => [ + 'title' => 'Koppel je AI-assistent', + 'description' => 'Voeg TryPost toe als MCP-server zodat je assistent social posts voor je kan maken en beheren.', + 'copied' => 'MCP-URL gekopieerd.', ], - 'referral_source_title' => 'Hoe heb je ons gevonden?', - 'referral_source_description' => 'Dit helpt ons te begrijpen hoe mensen TryPost ontdekken.', - 'referral_source' => [ - 'google' => 'Google of zoekmachine', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram of Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI-assistent (ChatGPT, Claude…)', - 'friend' => 'Vriend of collega', - 'blog' => 'Blog, nieuwsbrief of artikel', - 'other' => 'Iets anders', + 'social' => [ + 'title' => 'Koppel een social account', + 'description' => 'Kies minstens één netwerk waar TryPost je content kan publiceren.', + 'connected_elsewhere' => 'Je hebt al een account gekoppeld in een andere workspace, dus deze stap is klaar.', ], - 'connect' => [ - 'title' => 'Koppel je eerste netwerk', - 'description' => 'Koppel ten minste één social account om te beginnen met plannen. Je kunt er altijd meer toevoegen.', - 'must_connect' => 'Koppel ten minste één netwerk om door te gaan.', + 'first_post' => [ + 'title' => 'Maak je eerste post', + 'description' => 'Probeer deze startprompt met je gekoppelde assistent, of maak de post direct in TryPost.', + 'prompt_label' => 'Voorbeeldprompt', + 'sample_prompt' => 'Maak een vriendelijke social post die mijn merk introduceert en pas die aan voor elk gekoppeld netwerk.', + 'copy_prompt' => 'Prompt kopiëren', + 'copied' => 'Voorbeeldprompt gekopieerd.', + 'create_button' => 'Je eerste post maken', + 'or' => 'of', + ], + 'ready' => [ + 'title' => 'Je bent klaar om te publiceren', + 'description' => 'Alles staat. Ga door naar TryPost en begin je content te plannen.', ], ]; diff --git a/lang/nl/sidebar.php b/lang/nl/sidebar.php index 308e0ddf..212a3c99 100644 --- a/lang/nl/sidebar.php +++ b/lang/nl/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Workspace selecteren', 'create_workspace' => 'Workspace aanmaken', 'create_post' => 'Post aanmaken', - 'profile' => 'Profiel', 'my_account' => 'Mijn account', 'account_settings' => 'Account en facturatie', 'workspace_settings' => 'Workspace-instellingen', 'log_out' => 'Uitloggen', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Selecteren', - - 'theme' => 'Thema: :name', - 'theme_light' => 'Licht', - 'theme_dark' => 'Donker', - 'theme_system' => 'Systeem', - + 'workspace' => [ + 'connections' => 'Koppelingen', + 'signatures' => 'Handtekeningen', + 'labels' => 'Labels', + 'assets' => 'Assets', + 'mcp' => 'MCP', + ], 'language' => 'Taal: :name', - 'language_select' => 'Taal: Selecteren', - 'groups' => [ 'posts' => 'Posts', 'workspace' => 'Workspace', 'others' => 'Overige', ], - 'analytics' => 'Statistieken', 'automations' => 'Automatiseringen', - 'settings' => 'Instellingen', - + 'onboarding' => 'Aan de slag', + 'onboarding_hint' => 'Setup afronden', 'posts' => [ 'calendar' => 'Kalender', 'all' => 'Alle', @@ -41,22 +35,11 @@ 'posted' => 'Geplaatst', 'drafts' => 'Concepten', ], - - 'workspace' => [ - 'connections' => 'Koppelingen', - 'signatures' => 'Handtekeningen', - 'labels' => 'Labels', - 'assets' => 'Assets', - 'mcp' => 'MCP', - 'api_keys' => 'API-sleutels', - ], - 'notifications' => 'Meldingen', 'mark_all_read' => 'Alles als gelezen markeren', 'mark_as_read' => 'Als gelezen markeren', 'archive_all' => 'Alles archiveren', 'no_notifications' => 'Geen meldingen', - 'support' => [ 'docs' => 'Documentatie', 'referral' => 'Verdien 30% referral', diff --git a/lang/nl/welcome.php b/lang/nl/welcome.php index 46cbfe98..b51349ff 100644 --- a/lang/nl/welcome.php +++ b/lang/nl/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Hoe heb je ons gevonden?', 'referral_source_description' => 'Dit helpt ons te begrijpen hoe mensen TryPost ontdekken.', 'referral_source' => [ - 'google' => 'Google of zoekmachine', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram of Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI-assistent (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Directory (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Vriend of collega', - 'blog' => 'Blog, nieuwsbrief of artikel', + 'founder' => 'Oprichter', + 'blog' => 'Blog / newsletter', 'other' => 'Iets anders', ], ]; diff --git a/lang/pl/mcp.php b/lang/pl/mcp.php index 622626c6..e81748e6 100644 --- a/lang/pl/mcp.php +++ b/lang/pl/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Skopiuj URL serwera TryPost', 'open_step' => 'Otwórz asystenta AI', 'copy' => 'Kopiuj URL', - 'connect' => 'Połącz z :client', + 'connect' => 'Połącz :client', 'step_add' => 'Wklej nazwę, URL lub config poniżej do swojej aplikacji. Logowanie otworzy się w przeglądarce przy pierwszym połączeniu.', 'name_label' => 'Nazwa', 'url_label' => 'URL serwera', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Autoryzacja nie powiodła się - TryPost', + 'error_title' => 'Nie udało się połączyć', + 'error_body' => 'To żądanie autoryzacji jest nieprawidłowe lub wygasło. Zamknij to okno i spróbuj ponownie połączyć się z poziomu klienta MCP.', + 'error_code' => 'Błąd: :error', ], 'other_clients_title' => 'Inne aplikacje', diff --git a/lang/pl/onboarding.php b/lang/pl/onboarding.php index acc0e002..f2c2e793 100644 --- a/lang/pl/onboarding.php +++ b/lang/pl/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Witamy w TryPost', - 'description' => 'Powiedz nam, co najlepiej opisuje Ciebie lub Twoją firmę, abyśmy mogli dopasować Twoje doświadczenie.', - 'continue' => 'Kontynuuj', - 'personas' => [ - 'creator' => 'Twórca treści', - 'freelancer' => 'Freelancer', - 'developer' => 'Programista', - 'startup' => 'Startup', - 'agency' => 'Agencja', - 'small_business' => 'Mała firma', - 'marketer' => 'Marketingowiec', - 'online_store' => 'Sklep internetowy', - 'other' => 'Inne', + 'title' => 'Pierwsze kroki', + 'welcome' => 'Witaj w TryPost, :name', + 'description' => 'Wykonaj poniższe kroki, aby zobaczyć, jak działa TryPost, i opublikować pierwszy post.', + 'skip_step' => 'Pomiń ten krok', + 'continue' => 'Przejdź do TryPost', + 'status' => [ + 'complete' => 'Ukończone', + 'todo' => 'Do zrobienia', + 'skipped' => 'Pominięty', ], - 'goals_title' => 'Jaki jest Twój cel z TryPost?', - 'goals_description' => 'Wybierz wszystko, co pasuje, a my skonfigurujemy TryPost dla Ciebie.', - 'goals' => [ - 'save_time' => 'Oszczędzaj czas, publikując wszędzie naraz', - 'ai_content' => 'Generuj posty z AI TryPost', - 'use_mcp' => 'Twórz posty w Claude, ChatGPT lub Cursor', - 'plan_calendar' => 'Planuj posty w kalendarzu', - 'stay_on_brand' => 'Utrzymuj każdy post spójny z marką', - 'grow_audience' => 'Powiększaj grono odbiorców i zaangażowanie', - 'drive_sales' => 'Zdobywaj więcej ruchu i sprzedaży', - 'manage_clients' => 'Zarządzaj wieloma markami lub klientami', - 'just_exploring' => 'Na razie tylko się rozglądam', - 'other' => 'Coś innego', + 'mcp' => [ + 'title' => 'Połącz asystenta AI', + 'description' => 'Dodaj TryPost jako serwer MCP, aby asystent mógł tworzyć i zarządzać postami społecznościowymi za Ciebie.', + 'copied' => 'URL MCP skopiowany.', ], - 'referral_source_title' => 'Jak nas znalazłeś?', - 'referral_source_description' => 'To pomaga nam zrozumieć, jak ludzie odkrywają TryPost.', - 'referral_source' => [ - 'google' => 'Google lub wyszukiwarka', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram lub Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Asystent AI (ChatGPT, Claude…)', - 'friend' => 'Znajomy lub współpracownik', - 'blog' => 'Blog, newsletter lub artykuł', - 'other' => 'Coś innego', + 'social' => [ + 'title' => 'Połącz konto społecznościowe', + 'description' => 'Wybierz co najmniej jedną sieć, na której TryPost może publikować Twoje treści.', + 'connected_elsewhere' => 'Masz już połączone konto w innym workspace, więc ten krok jest ukończony.', ], - 'connect' => [ - 'title' => 'Połącz swoją pierwszą sieć', - 'description' => 'Połącz co najmniej jedno konto społecznościowe, aby zacząć planować. Więcej możesz dodać w dowolnym momencie.', - 'must_connect' => 'Połącz co najmniej jedną sieć, aby kontynuować.', + 'first_post' => [ + 'title' => 'Utwórz pierwszy post', + 'description' => 'Wypróbuj ten prompt startowy z podłączonym asystentem albo utwórz post bezpośrednio w TryPost.', + 'prompt_label' => 'Przykładowy prompt', + 'sample_prompt' => 'Utwórz przyjazny post społecznościowy przedstawiający moją markę i dostosuj go do każdej podłączonej sieci.', + 'copy_prompt' => 'Kopiuj prompt', + 'copied' => 'Przykładowy prompt skopiowany.', + 'create_button' => 'Utwórz pierwszy post', + 'or' => 'lub', + ], + 'ready' => [ + 'title' => 'Możesz już publikować', + 'description' => 'Wszystko gotowe. Przejdź do TryPost i zacznij planować treści.', ], ]; diff --git a/lang/pl/sidebar.php b/lang/pl/sidebar.php index 0771bc2e..f3bed7b8 100644 --- a/lang/pl/sidebar.php +++ b/lang/pl/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Wybierz przestrzeń roboczą', 'create_workspace' => 'Utwórz przestrzeń roboczą', 'create_post' => 'Utwórz post', - 'profile' => 'Profil', 'my_account' => 'Moje konto', 'account_settings' => 'Konto i płatności', 'workspace_settings' => 'Ustawienia workspace', 'log_out' => 'Wyloguj się', - - 'workspace' => 'Przestrzeń robocza: :name', - 'workspace_select' => 'Przestrzeń robocza: Wybierz', - - 'theme' => 'Motyw: :name', - 'theme_light' => 'Jasny', - 'theme_dark' => 'Ciemny', - 'theme_system' => 'Systemowy', - + 'workspace' => [ + 'connections' => 'Połączenia', + 'signatures' => 'Sygnatury', + 'labels' => 'Etykiety', + 'assets' => 'Zasoby', + 'mcp' => 'MCP', + ], 'language' => 'Język: :name', - 'language_select' => 'Język: Wybierz', - 'groups' => [ 'posts' => 'Posty', 'workspace' => 'Przestrzeń robocza', 'others' => 'Inne', ], - 'analytics' => 'Analityka', 'automations' => 'Automatyzacje', - 'settings' => 'Ustawienia', - + 'onboarding' => 'Pierwsze kroki', + 'onboarding_hint' => 'Dokończ konfigurację', 'posts' => [ 'calendar' => 'Kalendarz', 'all' => 'Wszystkie', @@ -41,22 +35,11 @@ 'posted' => 'Opublikowane', 'drafts' => 'Szkice', ], - - 'workspace' => [ - 'connections' => 'Połączenia', - 'signatures' => 'Sygnatury', - 'labels' => 'Etykiety', - 'assets' => 'Zasoby', - 'mcp' => 'MCP', - 'api_keys' => 'Klucze API', - ], - 'notifications' => 'Powiadomienia', 'mark_all_read' => 'Oznacz wszystkie jako przeczytane', 'mark_as_read' => 'Oznacz jako przeczytane', 'archive_all' => 'Zarchiwizuj wszystkie', 'no_notifications' => 'Brak powiadomień', - 'support' => [ 'docs' => 'Dokumentacja', 'referral' => 'Zarabiaj 30% z poleceń', diff --git a/lang/pl/welcome.php b/lang/pl/welcome.php index 6b4ff4ab..87f4bba0 100644 --- a/lang/pl/welcome.php +++ b/lang/pl/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Jak nas znalazłeś?', 'referral_source_description' => 'To pomaga nam zrozumieć, jak ludzie odkrywają TryPost.', 'referral_source' => [ - 'google' => 'Google lub wyszukiwarka', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram lub Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Asystent AI (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Katalog (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Znajomy lub współpracownik', - 'blog' => 'Blog, newsletter lub artykuł', + 'founder' => 'Założyciel', + 'blog' => 'Blog / newsletter', 'other' => 'Coś innego', ], ]; diff --git a/lang/pt-BR/mcp.php b/lang/pt-BR/mcp.php index ccfc4266..5bc6d430 100644 --- a/lang/pt-BR/mcp.php +++ b/lang/pt-BR/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Copie a URL do servidor TryPost', 'open_step' => 'Abra seu assistente de IA', 'copy' => 'Copiar URL', - 'connect' => 'Conectar com :client', + 'connect' => 'Conectar :client', 'step_add' => 'Cole o nome, a URL ou o config abaixo no seu app. O login abre no navegador na primeira conexão.', 'name_label' => 'Nome', 'url_label' => 'URL do servidor', @@ -45,6 +45,10 @@ 'search_workspace' => 'Buscar workspaces...', 'no_workspace_found' => 'Nenhum workspace encontrado', 'scope_mcp_use' => 'Usar o servidor MCP', + 'error_page_title' => 'Falha na autorização - TryPost', + 'error_title' => 'Não foi possível conectar', + 'error_body' => 'Este pedido de autorização é inválido ou expirou. Feche esta janela e tente conectar de novo pelo seu cliente MCP.', + 'error_code' => 'Erro: :error', ], 'other_clients_title' => 'Outros apps', diff --git a/lang/pt-BR/onboarding.php b/lang/pt-BR/onboarding.php index 84a91560..d55a848c 100644 --- a/lang/pt-BR/onboarding.php +++ b/lang/pt-BR/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Bem-vindo ao TryPost', - 'description' => 'Conte o que melhor descreve você ou seu negócio para personalizarmos sua experiência.', - 'continue' => 'Continuar', - 'personas' => [ - 'creator' => 'Criador de conteúdo', - 'freelancer' => 'Freelancer', - 'developer' => 'Desenvolvedor', - 'startup' => 'Startup', - 'agency' => 'Agência', - 'small_business' => 'Pequena empresa', - 'marketer' => 'Profissional de marketing', - 'online_store' => 'Loja online', - 'other' => 'Outro', + 'title' => 'Primeiros passos', + 'welcome' => 'Boas-vindas ao TryPost, :name', + 'description' => 'Siga os passos abaixo pra ver como o TryPost funciona e publicar seu primeiro post.', + 'skip_step' => 'Pular esta etapa', + 'continue' => 'Continuar no TryPost', + 'status' => [ + 'complete' => 'Concluído', + 'todo' => 'Pendente', + 'skipped' => 'Pulada', ], - 'goals_title' => 'Qual o seu objetivo com o TryPost?', - 'goals_description' => 'Marque tudo que faz sentido e a gente ajusta o TryPost pra você.', - 'goals' => [ - 'save_time' => 'Economizar tempo postando em todas as redes de uma vez', - 'ai_content' => 'Gerar posts com a IA do TryPost', - 'use_mcp' => 'Criar posts pelo Claude, ChatGPT ou Cursor', - 'plan_calendar' => 'Planejar meus posts num calendário', - 'stay_on_brand' => 'Manter a consistência da minha marca', - 'grow_audience' => 'Crescer minha audiência e engajamento', - 'drive_sales' => 'Conseguir mais tráfego e vendas', - 'manage_clients' => 'Gerenciar várias marcas ou clientes', - 'just_exploring' => 'Só dando uma olhada por enquanto', - 'other' => 'Outra coisa', + 'mcp' => [ + 'title' => 'Conecte seu assistente de IA', + 'description' => 'Adicione o TryPost como servidor MCP para o assistente criar e gerenciar posts por você.', + 'copied' => 'URL do MCP copiada.', ], - 'referral_source_title' => 'Como você nos encontrou?', - 'referral_source_description' => 'Isso nos ajuda a entender como as pessoas descobrem o TryPost.', - 'referral_source' => [ - 'google' => 'Google ou busca', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ou Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistente de IA (ChatGPT, Claude…)', - 'friend' => 'Amigo ou colega', - 'blog' => 'Blog, newsletter ou artigo', - 'other' => 'Outra coisa', + 'social' => [ + 'title' => 'Conecte uma rede social', + 'description' => 'Escolha pelo menos uma rede onde o TryPost possa publicar seu conteúdo.', + 'connected_elsewhere' => 'Você já conectou uma conta em outro workspace, então este passo está pronto.', ], - 'connect' => [ - 'title' => 'Conecte sua primeira rede', - 'description' => 'Vincule pelo menos uma conta social para começar a agendar. Você pode adicionar mais quando quiser.', - 'must_connect' => 'Conecte pelo menos uma rede para continuar.', + 'first_post' => [ + 'title' => 'Crie seu primeiro post', + 'description' => 'Use este prompt no seu assistente, ou crie o post direto no TryPost.', + 'prompt_label' => 'Prompt de exemplo', + 'sample_prompt' => 'Crie um post social amigável apresentando minha marca e adapte para cada rede conectada.', + 'copy_prompt' => 'Copiar prompt', + 'copied' => 'Prompt de exemplo copiado.', + 'create_button' => 'Criar seu primeiro post', + 'or' => 'ou', + ], + 'ready' => [ + 'title' => 'Tudo pronto pra publicar', + 'description' => 'Você já pode seguir. Continue no TryPost e comece a planejar seu conteúdo.', ], ]; diff --git a/lang/pt-BR/sidebar.php b/lang/pt-BR/sidebar.php index 6b30fe47..ed7f13a8 100644 --- a/lang/pt-BR/sidebar.php +++ b/lang/pt-BR/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Selecionar workspace', 'create_workspace' => 'Criar workspace', 'create_post' => 'Novo post', - 'profile' => 'Perfil', 'my_account' => 'Minha conta', 'account_settings' => 'Conta e cobrança', 'workspace_settings' => 'Configurações do workspace', 'log_out' => 'Sair', - - 'workspace' => 'Workspace: :name', - 'workspace_select' => 'Workspace: Selecionar', - - 'theme' => 'Tema: :name', - 'theme_light' => 'Claro', - 'theme_dark' => 'Escuro', - 'theme_system' => 'Sistema', - + 'workspace' => [ + 'connections' => 'Conexões', + 'signatures' => 'Assinaturas', + 'labels' => 'Etiquetas', + 'assets' => 'Mídias', + 'mcp' => 'MCP', + ], 'language' => 'Idioma: :name', - 'language_select' => 'Idioma: Selecionar', - 'groups' => [ 'posts' => 'Posts', 'workspace' => 'Workspace', 'others' => 'Outros', ], - 'analytics' => 'Analytics', 'automations' => 'Automações', - 'settings' => 'Configurações', - + 'onboarding' => 'Primeiros passos', + 'onboarding_hint' => 'Complete a configuração', 'posts' => [ 'calendar' => 'Calendário', 'all' => 'Todos', @@ -41,22 +35,11 @@ 'posted' => 'Publicados', 'drafts' => 'Rascunhos', ], - - 'workspace' => [ - 'connections' => 'Conexões', - 'signatures' => 'Assinaturas', - 'labels' => 'Etiquetas', - 'assets' => 'Mídias', - 'mcp' => 'MCP', - 'api_keys' => 'API Keys', - ], - 'notifications' => 'Notificações', 'mark_all_read' => 'Marcar tudo como lido', 'mark_as_read' => 'Marcar como lido', 'archive_all' => 'Arquivar tudo', 'no_notifications' => 'Sem notificações', - 'support' => [ 'docs' => 'Documentação', 'referral' => 'Ganhe 30% de indicação', diff --git a/lang/pt-BR/welcome.php b/lang/pt-BR/welcome.php index a1a61d90..3e474436 100644 --- a/lang/pt-BR/welcome.php +++ b/lang/pt-BR/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Como você nos encontrou?', 'referral_source_description' => 'Isso nos ajuda a entender como as pessoas descobrem o TryPost.', 'referral_source' => [ - 'google' => 'Google ou busca', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram ou Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Assistente de IA (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Diretório (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Amigo ou colega', - 'blog' => 'Blog, newsletter ou artigo', + 'founder' => 'Fundador', + 'blog' => 'Blog / newsletter', 'other' => 'Outra coisa', ], ]; diff --git a/lang/ru/mcp.php b/lang/ru/mcp.php index c6557cb5..a5271c72 100644 --- a/lang/ru/mcp.php +++ b/lang/ru/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Скопируйте URL сервера TryPost', 'open_step' => 'Откройте ИИ-ассистента', 'copy' => 'Копировать URL', - 'connect' => 'Подключить через :client', + 'connect' => 'Подключить :client', 'step_add' => 'Вставьте имя, URL или config ниже в своё приложение. Вход откроется в браузере при первом подключении.', 'name_label' => 'Имя', 'url_label' => 'URL сервера', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Ошибка авторизации - TryPost', + 'error_title' => 'Не удалось подключиться', + 'error_body' => 'Этот запрос авторизации недействителен или устарел. Закройте это окно и попробуйте подключиться снова из MCP-клиента.', + 'error_code' => 'Ошибка: :error', ], 'other_clients_title' => 'Другие приложения', diff --git a/lang/ru/onboarding.php b/lang/ru/onboarding.php index 93103c85..35d1c763 100644 --- a/lang/ru/onboarding.php +++ b/lang/ru/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Добро пожаловать в TryPost', - 'description' => 'Расскажите, что лучше всего описывает вас или ваш бизнес, чтобы мы могли настроить работу под вас.', - 'continue' => 'Продолжить', - 'personas' => [ - 'creator' => 'Автор контента', - 'freelancer' => 'Фрилансер', - 'developer' => 'Разработчик', - 'startup' => 'Стартап', - 'agency' => 'Агентство', - 'small_business' => 'Малый бизнес', - 'marketer' => 'Маркетолог', - 'online_store' => 'Интернет-магазин', - 'other' => 'Другое', + 'title' => 'Начало работы', + 'welcome' => 'Добро пожаловать в TryPost, :name', + 'description' => 'Выполните шаги ниже, чтобы увидеть, как работает TryPost, и опубликовать первый пост.', + 'skip_step' => 'Пропустить этот шаг', + 'continue' => 'Перейти в TryPost', + 'status' => [ + 'complete' => 'Готово', + 'todo' => 'Сделать', + 'skipped' => 'Пропущено', ], - 'goals_title' => 'Какова ваша цель с TryPost?', - 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', - 'goals' => [ - 'save_time' => 'Экономить время, публикуя всюду сразу', - 'ai_content' => 'Генерировать посты с ИИ TryPost', - 'use_mcp' => 'Создавать посты через Claude, ChatGPT или Cursor', - 'plan_calendar' => 'Планировать посты в календаре', - 'stay_on_brand' => 'Держать каждый пост в стиле бренда', - 'grow_audience' => 'Наращивать аудиторию и вовлечённость', - 'drive_sales' => 'Получать больше трафика и продаж', - 'manage_clients' => 'Управлять несколькими брендами или клиентами', - 'just_exploring' => 'Пока просто знакомлюсь', - 'other' => 'Что-то ещё', + 'mcp' => [ + 'title' => 'Подключите ИИ-ассистента', + 'description' => 'Добавьте TryPost как MCP-сервер, чтобы ассистент мог создавать и вести соцпосты за вас.', + 'copied' => 'URL MCP скопирован.', ], - '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' => 'ИИ-ассистент (ChatGPT, Claude…)', - 'friend' => 'Друг или коллега', - 'blog' => 'Блог, рассылка или статья', - 'other' => 'Что-то другое', + 'social' => [ + 'title' => 'Подключите соцсеть', + 'description' => 'Выберите хотя бы одну сеть, где TryPost сможет публиковать ваш контент.', + 'connected_elsewhere' => 'Вы уже подключили аккаунт в другом пространстве, так что этот шаг выполнен.', ], - 'connect' => [ - 'title' => 'Подключите первую сеть', - 'description' => 'Привяжите хотя бы один социальный аккаунт, чтобы начать планировать. Вы можете добавить другие в любой момент.', - 'must_connect' => 'Подключите хотя бы одну сеть, чтобы продолжить.', + 'first_post' => [ + 'title' => 'Создайте первый пост', + 'description' => 'Попробуйте этот стартовый промпт с подключённым ассистентом или создайте пост прямо в TryPost.', + 'prompt_label' => 'Пример промпта', + 'sample_prompt' => 'Создай дружелюбный соцпост с представлением моего бренда и адаптируй его для каждой подключённой сети.', + 'copy_prompt' => 'Копировать промпт', + 'copied' => 'Пример промпта скопирован.', + 'create_button' => 'Создать первый пост', + 'or' => 'или', + ], + 'ready' => [ + 'title' => 'Вы готовы публиковать', + 'description' => 'Всё настроено. Перейдите в TryPost и начните планировать контент.', ], ]; diff --git a/lang/ru/sidebar.php b/lang/ru/sidebar.php index be9c935d..eeedb76f 100644 --- a/lang/ru/sidebar.php +++ b/lang/ru/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Выберите рабочее пространство', 'create_workspace' => 'Создать рабочее пространство', 'create_post' => 'Создать пост', - 'profile' => 'Профиль', 'my_account' => 'Мой аккаунт', 'account_settings' => 'Аккаунт и оплата', 'workspace_settings' => 'Настройки workspace', 'log_out' => 'Выйти', - - 'workspace' => 'Рабочее пространство: :name', - 'workspace_select' => 'Рабочее пространство: выбрать', - - 'theme' => 'Тема: :name', - 'theme_light' => 'Светлая', - 'theme_dark' => 'Тёмная', - 'theme_system' => 'Системная', - + 'workspace' => [ + 'connections' => 'Подключения', + 'signatures' => 'Подписи', + 'labels' => 'Метки', + 'assets' => 'Медиафайлы', + 'mcp' => 'MCP', + ], 'language' => 'Язык: :name', - 'language_select' => 'Язык: выбрать', - 'groups' => [ 'posts' => 'Посты', 'workspace' => 'Рабочее пространство', 'others' => 'Прочее', ], - 'analytics' => 'Аналитика', 'automations' => 'Автоматизации', - 'settings' => 'Настройки', - + 'onboarding' => 'Начало работы', + 'onboarding_hint' => 'Завершите настройку', 'posts' => [ 'calendar' => 'Календарь', 'all' => 'Все', @@ -41,22 +35,11 @@ 'posted' => 'Опубликованные', 'drafts' => 'Черновики', ], - - 'workspace' => [ - 'connections' => 'Подключения', - 'signatures' => 'Подписи', - 'labels' => 'Метки', - 'assets' => 'Медиафайлы', - 'mcp' => 'MCP', - 'api_keys' => 'API-ключи', - ], - 'notifications' => 'Уведомления', 'mark_all_read' => 'Отметить все как прочитанные', 'mark_as_read' => 'Отметить как прочитанное', 'archive_all' => 'Архивировать все', 'no_notifications' => 'Нет уведомлений', - 'support' => [ 'docs' => 'Документация', 'referral' => 'Зарабатывайте 30% по реферальной программе', diff --git a/lang/ru/welcome.php b/lang/ru/welcome.php index ab9ec23b..41c41910 100644 --- a/lang/ru/welcome.php +++ b/lang/ru/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Как вы нас нашли?', 'referral_source_description' => 'Это помогает нам понять, как люди узнают о TryPost.', 'referral_source' => [ - 'google' => 'Google или поиск', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram или Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'ИИ-ассистент (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Каталог (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Друг или коллега', - 'blog' => 'Блог, рассылка или статья', + 'founder' => 'Основатель', + 'blog' => 'Blog / newsletter', 'other' => 'Что-то другое', ], ]; diff --git a/lang/tr/mcp.php b/lang/tr/mcp.php index 959afcf8..88a6411d 100644 --- a/lang/tr/mcp.php +++ b/lang/tr/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'TryPost sunucu URL’ini kopyala', 'open_step' => 'AI asistanını aç', 'copy' => 'URL’yi kopyala', - 'connect' => ':client ile bağlan', + 'connect' => ':client bağla', 'step_add' => 'Adı, URL’yi veya config’i aşağıdaki gibi uygulamanıza yapıştırın. İlk bağlantıda oturum açma tarayıcıda açılır.', 'name_label' => 'Ad', 'url_label' => 'Sunucu URL’si', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Yetkilendirme başarısız - TryPost', + 'error_title' => 'Bağlantı kurulamadı', + 'error_body' => 'Bu yetkilendirme isteği geçersiz veya süresi dolmuş. Bu pencereyi kapatın ve MCP istemcinizden yeniden bağlanmayı deneyin.', + 'error_code' => 'Hata: :error', ], 'other_clients_title' => 'Diğer uygulamalar', diff --git a/lang/tr/onboarding.php b/lang/tr/onboarding.php index 96efe708..37906266 100644 --- a/lang/tr/onboarding.php +++ b/lang/tr/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'TryPost\'a hoş geldiniz', - 'description' => 'Deneyiminizi kişiselleştirebilmemiz için sizi veya işinizi en iyi tanımlayan seçeneği belirtin.', - 'continue' => 'Devam et', - 'personas' => [ - 'creator' => 'İçerik üreticisi', - 'freelancer' => 'Serbest çalışan', - 'developer' => 'Geliştirici', - 'startup' => 'Girişim', - 'agency' => 'Ajans', - 'small_business' => 'Küçük işletme', - 'marketer' => 'Pazarlamacı', - 'online_store' => 'Çevrimiçi mağaza', - 'other' => 'Diğer', + 'title' => 'Başlarken', + 'welcome' => 'TryPost’a hoş geldin, :name', + 'description' => 'TryPost’un nasıl çalıştığını görmek ve ilk gönderini yayınlamak için aşağıdaki adımları izle.', + 'skip_step' => 'Bu adımı atla', + 'continue' => 'TryPost’a devam et', + 'status' => [ + 'complete' => 'Tamamlandı', + 'todo' => 'Yapılacak', + 'skipped' => 'Atlandı', ], - 'goals_title' => 'TryPost ile hedefiniz nedir?', - 'goals_description' => 'Size uyan her şeyi seçin, biz de TryPost\'u sizin için ayarlayalım.', - 'goals' => [ - 'save_time' => 'Her yere aynı anda paylaşarak zaman kazanmak', - 'ai_content' => 'TryPost AI ile gönderi oluşturmak', - 'use_mcp' => 'Claude, ChatGPT veya Cursor ile gönderi oluşturmak', - 'plan_calendar' => 'Gönderilerimi bir takvimde planlamak', - 'stay_on_brand' => 'Her gönderiyi marka çizgisinde tutmak', - 'grow_audience' => 'Kitlemi ve etkileşimimi büyütmek', - 'drive_sales' => 'Daha fazla trafik ve satış elde etmek', - 'manage_clients' => 'Birden fazla marka veya müşteri yönetmek', - 'just_exploring' => 'Şimdilik sadece keşfetmek', - 'other' => 'Başka bir şey', + 'mcp' => [ + 'title' => 'AI asistanını bağla', + 'description' => 'Asistanının senin için sosyal gönderiler oluşturup yönetebilmesi için TryPost’u MCP sunucusu olarak ekle.', + 'copied' => 'MCP URL’si kopyalandı.', ], - 'referral_source_title' => 'Bizi nasıl buldunuz?', - 'referral_source_description' => 'İnsanların TryPost\'u nasıl keşfettiğini anlamamıza yardımcı olur.', - 'referral_source' => [ - 'google' => 'Google veya arama', - 'x' => 'X (Twitter)', - 'linkedin' => 'LinkedIn', - 'youtube' => 'YouTube', - 'tiktok' => 'TikTok', - 'instagram' => 'Instagram veya Threads', - 'reddit' => 'Reddit', - 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Yapay zeka asistanı (ChatGPT, Claude…)', - 'friend' => 'Arkadaş veya meslektaş', - 'blog' => 'Blog, bülten veya makale', - 'other' => 'Başka bir şey', + 'social' => [ + 'title' => 'Bir sosyal hesap bağla', + 'description' => 'TryPost’un içeriğini yayınlayabileceği en az bir ağ seç.', + 'connected_elsewhere' => 'Başka bir çalışma alanında zaten bir hesap bağladın, bu adım tamam.', ], - 'connect' => [ - 'title' => 'İlk ağınızı bağlayın', - 'description' => 'Zamanlamaya başlamak için en az bir sosyal hesap bağlayın. İstediğiniz zaman daha fazlasını ekleyebilirsiniz.', - 'must_connect' => 'Devam etmek için en az bir ağ bağlayın.', + 'first_post' => [ + 'title' => 'İlk gönderini oluştur', + 'description' => 'Bu başlangıç prompt’unu bağlı asistanınla dene veya gönderiyi doğrudan TryPost’ta oluştur.', + 'prompt_label' => 'Örnek prompt', + 'sample_prompt' => 'Markamı tanıtan samimi bir sosyal gönderi oluştur ve bağlı her ağ için uyarla.', + 'copy_prompt' => 'Prompt’u kopyala', + 'copied' => 'Örnek prompt kopyalandı.', + 'create_button' => 'İlk gönderini oluştur', + 'or' => 'veya', + ], + 'ready' => [ + 'title' => 'Yayınlamaya hazırsın', + 'description' => 'Her şey hazır. TryPost’a devam et ve içeriğini planlamaya başla.', ], ]; diff --git a/lang/tr/sidebar.php b/lang/tr/sidebar.php index 46e5d573..64a8532c 100644 --- a/lang/tr/sidebar.php +++ b/lang/tr/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Çalışma alanı seç', 'create_workspace' => 'Çalışma alanı oluştur', 'create_post' => 'Gönderi oluştur', - 'profile' => 'Profil', 'my_account' => 'Hesabım', 'account_settings' => 'Hesap ve faturalandırma', 'workspace_settings' => 'Çalışma alanı ayarları', 'log_out' => 'Çıkış yap', - - 'workspace' => 'Çalışma alanı: :name', - 'workspace_select' => 'Çalışma alanı: Seç', - - 'theme' => 'Tema: :name', - 'theme_light' => 'Açık', - 'theme_dark' => 'Koyu', - 'theme_system' => 'Sistem', - + 'workspace' => [ + 'connections' => 'Bağlantılar', + 'signatures' => 'İmzalar', + 'labels' => 'Etiketler', + 'assets' => 'Varlıklar', + 'mcp' => 'MCP', + ], 'language' => 'Dil: :name', - 'language_select' => 'Dil: Seç', - 'groups' => [ 'posts' => 'Gönderiler', 'workspace' => 'Çalışma Alanı', 'others' => 'Diğerleri', ], - 'analytics' => 'Analitik', 'automations' => 'Otomasyonlar', - 'settings' => 'Ayarlar', - + 'onboarding' => 'Başlarken', + 'onboarding_hint' => 'Kurulumu bitir', 'posts' => [ 'calendar' => 'Takvim', 'all' => 'Tümü', @@ -41,22 +35,11 @@ 'posted' => 'Paylaşılan', 'drafts' => 'Taslaklar', ], - - 'workspace' => [ - 'connections' => 'Bağlantılar', - 'signatures' => 'İmzalar', - 'labels' => 'Etiketler', - 'assets' => 'Varlıklar', - 'mcp' => 'MCP', - 'api_keys' => 'API Anahtarları', - ], - 'notifications' => 'Bildirimler', 'mark_all_read' => 'Tümünü okundu olarak işaretle', 'mark_as_read' => 'Okundu olarak işaretle', 'archive_all' => 'Tümünü arşivle', 'no_notifications' => 'Bildirim yok', - 'support' => [ 'docs' => 'Dokümantasyon', 'referral' => '%30 referans kazanın', diff --git a/lang/tr/welcome.php b/lang/tr/welcome.php index 40306fd3..74f9dc6f 100644 --- a/lang/tr/welcome.php +++ b/lang/tr/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Bizi nasıl buldunuz?', 'referral_source_description' => 'İnsanların TryPost\'u nasıl keşfettiğini anlamamıza yardımcı olur.', 'referral_source' => [ - 'google' => 'Google veya arama', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram veya Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'Yapay zeka asistanı (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Dizin (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Arkadaş veya meslektaş', - 'blog' => 'Blog, bülten veya makale', + 'founder' => 'Kurucu', + 'blog' => 'Blog / newsletter', 'other' => 'Başka bir şey', ], ]; diff --git a/lang/uk/mcp.php b/lang/uk/mcp.php index fa619293..c9efa42c 100644 --- a/lang/uk/mcp.php +++ b/lang/uk/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => 'Скопіюйте URL сервера TryPost', 'open_step' => 'Відкрийте свого ІІ-асистента', 'copy' => 'Скопіювати URL', - 'connect' => 'Підключити через :client', + 'connect' => 'Підключити :client', 'step_add' => 'Вставте назву, URL або config нижче у свій застосунок. Вхід відкриється в браузері під час першого підключення.', 'name_label' => 'Назва', 'url_label' => 'URL сервера', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => 'Помилка авторизації - TryPost', + 'error_title' => 'Не вдалося підключитися', + 'error_body' => 'Цей запит на авторизацію недійсний або застарів. Закрийте це вікно й спробуйте підключитися знову з MCP-клієнта.', + 'error_code' => 'Помилка: :error', ], 'other_clients_title' => 'Інші застосунки', diff --git a/lang/uk/onboarding.php b/lang/uk/onboarding.php index b0daabb4..abc5e1b2 100644 --- a/lang/uk/onboarding.php +++ b/lang/uk/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => 'Ласкаво просимо до TryPost', - 'description' => 'Розкажіть, що найкраще описує вас або ваш бізнес, щоб ми могли адаптувати ваш досвід.', - 'continue' => 'Продовжити', - 'personas' => [ - 'creator' => 'Автор контенту', - 'freelancer' => 'Фрілансер', - 'developer' => 'Розробник', - 'startup' => 'Стартап', - 'agency' => 'Агенція', - 'small_business' => 'Малий бізнес', - 'marketer' => 'Маркетолог', - 'online_store' => 'Інтернет-магазин', - 'other' => 'Інше', + 'title' => 'Початок роботи', + 'welcome' => 'Ласкаво просимо до TryPost, :name', + 'description' => 'Виконайте кроки нижче, щоб зрозуміти, як працює TryPost, і опублікувати свій перший пост.', + 'skip_step' => 'Пропустити цей крок', + 'continue' => 'Продовжити до TryPost', + 'status' => [ + 'complete' => 'Готово', + 'todo' => 'До виконання', + 'skipped' => 'Пропущено', ], - 'goals_title' => 'Яка ваша мета з TryPost?', - 'goals_description' => 'Виберіть усе, що підходить, і ми налаштуємо TryPost для вас.', - 'goals' => [ - 'save_time' => 'Економити час, публікуючи всюди одразу', - 'ai_content' => 'Генерувати пости з AI TryPost', - 'use_mcp' => 'Створювати пости через Claude, ChatGPT або Cursor', - 'plan_calendar' => 'Планувати пости в календарі', - 'stay_on_brand' => 'Тримати кожен пост у стилі бренду', - 'grow_audience' => 'Збільшувати аудиторію та залучення', - 'drive_sales' => 'Отримувати більше трафіку та продажів', - 'manage_clients' => 'Керувати кількома брендами або клієнтами', - 'just_exploring' => 'Поки що просто досліджую', - 'other' => 'Щось інше', + 'mcp' => [ + 'title' => 'Підключіть свого AI-асистента', + 'description' => 'Додайте TryPost як MCP-сервер, щоб асистент міг створювати й керувати соцмережевими постами за вас.', + 'copied' => 'MCP URL скопійовано.', ], - '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' => 'Щось інше', + 'social' => [ + 'title' => 'Підключіть соціальний акаунт', + 'description' => 'Оберіть принаймні одну мережу, у якій TryPost зможе публікувати ваш контент.', + 'connected_elsewhere' => 'Ви вже підключили акаунт в іншому робочому просторі, тож цей крок виконано.', ], - 'connect' => [ - 'title' => 'Підключіть першу мережу', - 'description' => 'Під’єднайте хоча б один соціальний акаунт, щоб почати планування. Ви зможете додати більше будь-коли.', - 'must_connect' => 'Підключіть хоча б одну мережу, щоб продовжити.', + 'first_post' => [ + 'title' => 'Створіть свій перший пост', + 'description' => 'Спробуйте цей стартовий промпт із підключеним асистентом або створіть пост безпосередньо в TryPost.', + 'prompt_label' => 'Приклад промпту', + 'sample_prompt' => 'Створи дружній пост, що представляє мій бренд, і адаптуй його для кожної підключеної мережі.', + 'copy_prompt' => 'Скопіювати промпт', + 'copied' => 'Приклад промпту скопійовано.', + 'create_button' => 'Створити перший пост', + 'or' => 'або', + ], + 'ready' => [ + 'title' => 'Ви готові публікувати', + 'description' => 'Усе налаштовано. Продовжуйте в TryPost і починайте планувати контент.', ], ]; diff --git a/lang/uk/sidebar.php b/lang/uk/sidebar.php index ad4194ca..399a8962 100644 --- a/lang/uk/sidebar.php +++ b/lang/uk/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => 'Виберіть робочий простір', 'create_workspace' => 'Створити робочий простір', 'create_post' => 'Створити пост', - 'profile' => 'Профіль', 'my_account' => 'Мій акаунт', 'account_settings' => 'Акаунт і оплата', 'workspace_settings' => 'Налаштування робочого простору', 'log_out' => 'Вийти', - - 'workspace' => 'Робочий простір: :name', - 'workspace_select' => 'Робочий простір: вибрати', - - 'theme' => 'Тема: :name', - 'theme_light' => 'Світла', - 'theme_dark' => 'Темна', - 'theme_system' => 'Системна', - + 'workspace' => [ + 'connections' => 'Підключення', + 'signatures' => 'Підписи', + 'labels' => 'Мітки', + 'assets' => 'Медіафайли', + 'mcp' => 'MCP', + ], 'language' => 'Мова: :name', - 'language_select' => 'Мова: вибрати', - 'groups' => [ 'posts' => 'Пости', 'workspace' => 'Робочий простір', 'others' => 'Інше', ], - 'analytics' => 'Аналітика', 'automations' => 'Автоматизації', - 'settings' => 'Налаштування', - + 'onboarding' => 'Початок роботи', + 'onboarding_hint' => 'Завершіть налаштування', 'posts' => [ 'calendar' => 'Календар', 'all' => 'Усі', @@ -41,22 +35,11 @@ 'posted' => 'Опубліковані', 'drafts' => 'Чернетки', ], - - 'workspace' => [ - 'connections' => 'Підключення', - 'signatures' => 'Підписи', - 'labels' => 'Мітки', - 'assets' => 'Медіафайли', - 'mcp' => 'MCP', - 'api_keys' => 'API-ключі', - ], - 'notifications' => 'Сповіщення', 'mark_all_read' => 'Позначити все прочитаним', 'mark_as_read' => 'Позначити прочитаним', 'archive_all' => 'Архівувати все', 'no_notifications' => 'Немає сповіщень', - 'support' => [ 'docs' => 'Документація', 'referral' => 'Отримуйте 30% за рефералами', diff --git a/lang/uk/welcome.php b/lang/uk/welcome.php index fd44a0c9..80967c87 100644 --- a/lang/uk/welcome.php +++ b/lang/uk/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => 'Як ви нас знайшли?', 'referral_source_description' => 'Це допомагає нам зрозуміти, як люди дізнаються про TryPost.', 'referral_source' => [ - 'google' => 'Google або пошук', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram або Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI-асистент (ChatGPT, Claude…)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => 'Каталог (G2, Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => 'Друг або колега', - 'blog' => 'Блог, розсилка або стаття', + 'founder' => 'Засновник', + 'blog' => 'Blog / newsletter', 'other' => 'Щось інше', ], ]; diff --git a/lang/zh/mcp.php b/lang/zh/mcp.php index f72c3af6..87cc5709 100644 --- a/lang/zh/mcp.php +++ b/lang/zh/mcp.php @@ -8,7 +8,7 @@ 'copy_step' => '复制你的 TryPost 服务器 URL', 'open_step' => '打开你的 AI 助手', 'copy' => '复制 URL', - 'connect' => '使用 :client 连接', + 'connect' => '连接 :client', 'step_add' => '将下方的名称、URL 或配置粘贴到你的应用中。首次连接时会在浏览器中打开登录。', 'name_label' => '名称', 'url_label' => '服务器 URL', @@ -45,6 +45,10 @@ 'search_workspace' => 'Search workspaces...', 'no_workspace_found' => 'No workspace found', 'scope_mcp_use' => 'Use MCP server', + 'error_page_title' => '授权失败 - TryPost', + 'error_title' => '无法连接', + 'error_body' => '此授权请求无效或已过期。请关闭此窗口,然后从 MCP 客户端重新连接。', + 'error_code' => '错误::error', ], 'other_clients_title' => '其他应用', diff --git a/lang/zh/onboarding.php b/lang/zh/onboarding.php index 294a652c..228ef1b2 100644 --- a/lang/zh/onboarding.php +++ b/lang/zh/onboarding.php @@ -3,53 +3,38 @@ declare(strict_types=1); return [ - 'title' => '欢迎使用 TryPost', - 'description' => '告诉我们最能描述你或你业务的选项,以便我们为你定制体验。', - 'continue' => '继续', - 'personas' => [ - 'creator' => '内容创作者', - 'freelancer' => '自由职业者', - 'developer' => '开发者', - 'startup' => '初创公司', - 'agency' => '代理机构', - 'small_business' => '小型企业', - 'marketer' => '营销人员', - 'online_store' => '网店', - 'other' => '其他', + 'title' => '开始使用', + 'welcome' => '欢迎使用 TryPost,:name', + 'description' => '按下面的步骤了解 TryPost 的用法,并发布你的第一条内容。', + 'skip_step' => '跳过此步骤', + 'continue' => '继续前往 TryPost', + 'status' => [ + 'complete' => '已完成', + 'todo' => '待完成', + 'skipped' => '已跳过', ], - 'goals_title' => '你使用 TryPost 的目标是什么?', - 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', - 'goals' => [ - 'save_time' => '一次发布到所有平台,节省时间', - 'ai_content' => '用 TryPost AI 生成帖子', - 'use_mcp' => '通过 Claude、ChatGPT 或 Cursor 创建帖子', - 'plan_calendar' => '在日历上规划我的帖子', - 'stay_on_brand' => '让每一条帖子都符合品牌调性', - 'grow_audience' => '增长我的受众和互动', - 'drive_sales' => '获得更多流量和销量', - 'manage_clients' => '管理多个品牌或客户', - 'just_exploring' => '目前只是随便看看', - 'other' => '其他需求', + 'mcp' => [ + 'title' => '连接你的 AI 助手', + 'description' => '将 TryPost 添加为 MCP 服务器,让助手帮你创建和管理社交内容。', + 'copied' => '已复制 MCP URL。', ], - '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' => '其他', + 'social' => [ + 'title' => '连接社交账号', + 'description' => '至少选择一个网络,让 TryPost 可以发布你的内容。', + 'connected_elsewhere' => '你已在其他工作空间连接了账户,因此这一步已完成。', ], - 'connect' => [ - 'title' => '连接你的第一个平台', - 'description' => '至少关联一个社交账号即可开始排期。你可以随时添加更多。', - 'must_connect' => '请至少连接一个平台以继续。', + 'first_post' => [ + 'title' => '创建第一条内容', + 'description' => '用已连接的助手试试这个入门提示词,或直接在 TryPost 中创建内容。', + 'prompt_label' => '示例提示词', + 'sample_prompt' => '写一条友好的社交内容介绍我的品牌,并为每个已连接的网络做适配。', + 'copy_prompt' => '复制提示词', + 'copied' => '已复制示例提示词。', + 'create_button' => '创建第一条内容', + 'or' => '或', + ], + 'ready' => [ + 'title' => '可以开始发布了', + 'description' => '一切就绪。继续前往 TryPost,开始规划你的内容。', ], ]; diff --git a/lang/zh/sidebar.php b/lang/zh/sidebar.php index d9e406c4..23dc2e1a 100644 --- a/lang/zh/sidebar.php +++ b/lang/zh/sidebar.php @@ -7,33 +7,27 @@ 'select_workspace' => '选择工作区', 'create_workspace' => '创建工作区', 'create_post' => '创建帖子', - 'profile' => '个人资料', 'my_account' => '我的账户', 'account_settings' => '账户与账单', 'workspace_settings' => '工作区设置', 'log_out' => '退出登录', - - 'workspace' => '工作区::name', - 'workspace_select' => '工作区:选择', - - 'theme' => '主题::name', - 'theme_light' => '浅色', - 'theme_dark' => '深色', - 'theme_system' => '跟随系统', - + 'workspace' => [ + 'connections' => '连接', + 'signatures' => '签名', + 'labels' => '标签', + 'assets' => '素材库', + 'mcp' => 'MCP', + ], 'language' => '语言::name', - 'language_select' => '语言:选择', - 'groups' => [ 'posts' => '帖子', 'workspace' => '工作区', 'others' => '其他', ], - 'analytics' => '分析', 'automations' => '自动化', - 'settings' => '设置', - + 'onboarding' => '开始使用', + 'onboarding_hint' => '完成设置', 'posts' => [ 'calendar' => '日历', 'all' => '全部', @@ -41,22 +35,11 @@ 'posted' => '已发布', 'drafts' => '草稿', ], - - 'workspace' => [ - 'connections' => '连接', - 'signatures' => '签名', - 'labels' => '标签', - 'assets' => '素材库', - 'mcp' => 'MCP', - 'api_keys' => 'API 密钥', - ], - 'notifications' => '通知', 'mark_all_read' => '全部标为已读', 'mark_as_read' => '标为已读', 'archive_all' => '全部归档', 'no_notifications' => '暂无通知', - 'support' => [ 'docs' => '文档', 'referral' => '赚取 30% 推荐奖励', diff --git a/lang/zh/welcome.php b/lang/zh/welcome.php index d6e769a1..06781f02 100644 --- a/lang/zh/welcome.php +++ b/lang/zh/welcome.php @@ -41,17 +41,22 @@ 'referral_source_title' => '您是如何找到我们的?', 'referral_source_description' => '这有助于我们了解人们是如何发现 TryPost 的。', 'referral_source' => [ - 'google' => 'Google 或搜索', + 'google' => 'Google', 'x' => 'X (Twitter)', 'linkedin' => 'LinkedIn', 'youtube' => 'YouTube', 'tiktok' => 'TikTok', - 'instagram' => 'Instagram 或 Threads', + 'instagram' => 'Instagram', + 'threads' => 'Threads', 'reddit' => 'Reddit', 'product_hunt' => 'Product Hunt', - 'ai_assistant' => 'AI 助手(ChatGPT、Claude 等)', + 'github' => 'GitHub', + 'hacker_news' => 'Hacker News', + 'directories' => '软件目录(G2、Capterra…)', + 'ai_assistant' => 'ChatGPT / Claude', 'friend' => '朋友或同事', - 'blog' => '博客、新闻通讯或文章', + 'founder' => '创始人', + 'blog' => 'Blog / newsletter', 'other' => '其他', ], ]; diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 218625ff..29a0353e 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -30,6 +30,7 @@ import { import NavMain from '@/components/NavMain.vue'; import NavSupport from '@/components/NavSupport.vue'; import NotificationBell from '@/components/NotificationBell.vue'; +import SidebarOnboarding from '@/components/onboarding/SidebarOnboarding.vue'; import { Avatar } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { @@ -282,6 +283,8 @@ const bottomNavItems = computed(() => [ + +
{