diff --git a/.cursor/rules/tests-dusk.mdc b/.cursor/rules/tests-dusk.mdc index 26984c76d..e88104732 100644 --- a/.cursor/rules/tests-dusk.mdc +++ b/.cursor/rules/tests-dusk.mdc @@ -1,10 +1,10 @@ --- -description: Laravel Dusk browser tests — named routes, dusk selectors, no CSS/text-based assertions +description: Browser tests (Pest + Playwright) — named routes, data-testid selectors, no CSS/text-based assertions globs: tests/Browser/**/*.php alwaysApply: false --- -# Dusk Browser Tests +# Browser Tests ## Named routes (IMPORTANT) @@ -12,32 +12,40 @@ ALWAYS use named routes via the `route()` helper. NEVER hardcode URLs like `'htt ```php // BAD -$browser->visit('https://trypost.test/login'); +visit('https://trypost.test/login'); // GOOD -$browser->visit(route('login')); +visit(route('login')); ``` -## Dusk selectors +## Selectors (IMPORTANT) -ALWAYS use `dusk` selectors (`@selector-name`) for interactions and assertions. NEVER use CSS classes (`.text-red-600`), tag names, or text strings. +ALWAYS use `data-testid` attributes and target them with `@selector-name`. NEVER use CSS classes (`.text-red-600`), tag names, or text strings. -Add `dusk="my-element"` attributes to Vue components and target them with `$browser->click('@my-element')`, `$browser->waitFor('@my-element')`, etc. +The Pest browser plugin resolves `@selector` against `[data-testid]` / `[data-test]` only — `dusk="..."` attributes are NOT matched (legacy from Laravel Dusk). + +Add `data-testid="my-element"` attributes to Vue components and target them with `$page->click('@my-element')`, `$page->assertVisible('@my-element')`, etc. ```php // BAD -$browser->waitFor('.text-red-600'); -$browser->click('button.primary'); -$browser->assertSee('Welcome'); +$page->assertVisible('.text-red-600'); +$page->click('button.primary'); // GOOD -$browser->waitFor('@input-error'); -$browser->click('@submit-button'); -$browser->assertVisible('@welcome-message'); +$page->assertVisible('@input-error'); +$page->click('@submit-button'); ``` ```vue - -{{ error }} + +{{ error }} ``` + +## No auto-wait + +Browser assertions do not auto-wait. Poll browser-side (see `waitForTestId` / `waitForDusk` helpers in existing tests) before asserting on async UI. + +## Strict models + +Use `->fresh()` when acting as a factory-created user — the in-process server keeps the guard user across requests, and factory instances lack nullable columns (strict mode throws `MissingAttributeException`). diff --git a/.env.example b/.env.example index 4a631e7ee..041b3168f 100644 --- a/.env.example +++ b/.env.example @@ -191,11 +191,17 @@ STRIPE_SECRET= STRIPE_WEBHOOK_SECRET= # Set to false to allow generic signup trial without requiring a card. REQUIRE_CARD_FOR_TRIAL=true -# Free trial length in days, used only for the no-card generic trial above. +# Checkout trial length in days (card collected up front when REQUIRE_CARD_FOR_TRIAL=true). +# - >0: Stripe Checkout trial for N days for first-time signups only (no prior +# real subscription). Values below 2 are clamped to 2 (Stripe's 48h min). +# - 0: no Checkout trial; charge starts immediately. +# - Returning / canceled accounts never get another Checkout trial. +# - When REQUIRE_CARD_FOR_TRIAL=false, Checkout never applies a trial (generic +# signup trial uses this value instead; must be >= 1 in that mode). +# Empty = default 8. CASHIER_TRIAL_DAYS=8 -# Stripe Coupon ID (amount_off, duration=once) so the first invoice at -# checkout comes out to $1 instead of the full monthly price. -STRIPE_FIRST_MONTH_COUPON_ID= +# Show Stripe Checkout promotion-code field. +CASHIER_ALLOW_PROMOTION_CODES=true # Stripe Plan Price IDs (one per plan × interval). Used by PlanSeeder. STRIPE_WORKSPACE_MONTHLY= diff --git a/CLAUDE.md b/CLAUDE.md index 709aa853f..2981ed280 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -306,13 +306,16 @@ Vue components must have a single root element. - Example: `$this->postJson(route('app.posts.store'))` instead of `$this->postJson('/posts')`. - With params: `route('app.posts.ai.create.finalize', $creationId)`. -## Dusk (Browser Tests) - -- In Dusk tests, ALWAYS use named routes via `route()` helper. NEVER hardcode URLs like `'https://trypost.test/login'`. - - Example: `$browser->visit(route('login'))` instead of `$browser->visit('https://trypost.test/login')`. -- ALWAYS use `dusk` selectors (`@selector-name`) for interacting with and asserting elements. NEVER use CSS classes (`.text-red-600`), tag names, or text strings. - - Add `dusk="my-element"` attributes to Vue components and use `$browser->click('@my-element')`, `$browser->waitFor('@my-element')`, etc. - - Example: `$browser->waitFor('@input-error')` instead of `$browser->waitFor('.text-red-600')`. +## Browser Tests + +- In browser tests, ALWAYS use named routes via `route()` helper. NEVER hardcode URLs like `'https://trypost.test/login'`. + - Example: `visit(route('login'))` instead of `visit('https://trypost.test/login')`. +- ALWAYS use `data-testid` selectors (`@selector-name`) for interacting with and asserting elements. NEVER use CSS classes (`.text-red-600`), tag names, or text strings. + - The Pest browser plugin resolves `@selector` against `[data-testid]` / `[data-test]` only — `dusk="..."` attributes are NOT matched. + - Add `data-testid="my-element"` attributes to Vue components and use `$page->click('@my-element')`, `$page->assertVisible('@my-element')`, etc. + - Example: `$page->assertVisible('@input-error')` instead of `$page->assertVisible('.text-red-600')`. +- Assertions do not auto-wait — poll browser-side (see `waitForTestId`/`waitForDusk` helpers in `tests/Browser`) before asserting on async UI. +- Use `->fresh()` when acting as a factory-created user: the in-process server keeps the guard user across requests, and factory instances lack nullable columns (strict mode throws `MissingAttributeException`). ## Array Data Access diff --git a/app/Actions/Billing/StartSubscriptionCheckout.php b/app/Actions/Billing/StartSubscriptionCheckout.php index 949defc1b..bbcb1a1f6 100644 --- a/app/Actions/Billing/StartSubscriptionCheckout.php +++ b/app/Actions/Billing/StartSubscriptionCheckout.php @@ -5,7 +5,7 @@ namespace App\Actions\Billing; use App\Models\Account; -use App\Support\Billing\FirstMonthCheckoutDiscount; +use App\Support\Billing\ConfigureSubscriptionCheckout; use Inertia\Inertia; use Symfony\Component\HttpFoundation\Response; @@ -13,9 +13,8 @@ class StartSubscriptionCheckout { /** * Create a Stripe Checkout session for the given price and return an Inertia - * redirect to it. Quantity tracks the account's workspace count; the first - * month's coupon is applied when the instance requires a card up front, so - * the first invoice charges $1 instead of running a $0 trial authorization. + * redirect to it. Quantity tracks the account's workspace count. Trial days + * and promotion codes come from Cashier env config. */ public function redirect(Account $account, string $priceId, string $cancelUrl): Response { @@ -27,7 +26,7 @@ public function redirect(Account $account, string $priceId, string $cancelUrl): $subscription = $account->newSubscription(Account::SUBSCRIPTION_NAME, $priceId) ->quantity(max(1, $account->workspaces()->count())); - FirstMonthCheckoutDiscount::apply($subscription, $account); + ConfigureSubscriptionCheckout::apply($subscription, $account); $session = $subscription->checkout([ 'success_url' => route('app.billing.processing').'?session_id={CHECKOUT_SESSION_ID}', diff --git a/app/Actions/Onboarding/ResolveOnboardingStatus.php b/app/Actions/Onboarding/ResolveOnboardingStatus.php new file mode 100644 index 000000000..7c2715ec5 --- /dev/null +++ b/app/Actions/Onboarding/ResolveOnboardingStatus.php @@ -0,0 +1,287 @@ +account; + + if ($account?->onboarding_completed_at !== null) { + return [ + 'mcp_connected' => true, + 'social_connected' => true, + 'first_post_created' => true, + 'all_complete' => true, + 'show_residual' => false, + 'completed_at' => $account->onboarding_completed_at->toIso8601String(), + 'dismissed_at' => $account->onboarding_dismissed_at?->toIso8601String(), + ]; + } + + // All three steps are account-scoped so checklist checkmarks, residual + // progress, and cross-workspace completion stay aligned. + $mcpConnected = $this->accountHasMcpConnection($account); + $socialConnected = $this->accountHasSocialConnection($account); + $firstPostCreated = $this->accountHasPost($account); + $allComplete = $mcpConnected && $socialConnected && $firstPostCreated; + + $showResidual = ! config('trypost.self_hosted') + && $user->isAccountOwner() + && ($account?->hasAppAccess() ?? false) + && $account->onboarding_dismissed_at === null + && ! $allComplete; + + return [ + 'mcp_connected' => $mcpConnected, + 'social_connected' => $socialConnected, + 'first_post_created' => $firstPostCreated, + 'all_complete' => $allComplete, + 'show_residual' => $showResidual, + 'completed_at' => null, + 'dismissed_at' => $account?->onboarding_dismissed_at?->toIso8601String(), + ]; + } + + /** + * Read checklist state, capture step analytics, and stamp completion when done. + * Call from intentional onboarding surfaces — not from shared Inertia props. + * + * @return array{ + * mcp_connected: bool, + * social_connected: bool, + * first_post_created: bool, + * all_complete: bool, + * show_residual: bool, + * completed_at: ?string, + * dismissed_at: ?string + * } + */ + public function syncProgress(User $user): array + { + $status = $this->handle($user); + $account = $user->account; + + if ($account === null || $account->onboarding_completed_at !== null) { + return $status; + } + + if ($account->onboarding_dismissed_at === null) { + $this->captureCompletedStep($user, $account, 'mcp_connected', $status['mcp_connected']); + $this->captureCompletedStep($user, $account, 'social_connected', $status['social_connected']); + $this->captureCompletedStep($user, $account, 'first_post_created', $status['first_post_created']); + } + + if ($account->onboarding_dismissed_at !== null) { + return $status; + } + + if ($status['all_complete']) { + $this->markCompleted($user); + $account->refresh(); + + return [ + ...$status, + 'show_residual' => false, + 'completed_at' => $account->onboarding_completed_at?->toIso8601String(), + ]; + } + + return $status; + } + + /** + * Stamp account onboarding completion once and capture the funnel event. + */ + public function markCompleted(User $user): bool + { + $account = $user->account; + + if ( + $account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return false; + } + + $completedAt = now(); + + $updated = Account::query() + ->whereKey($account->id) + ->whereNull('onboarding_completed_at') + ->whereNull('onboarding_dismissed_at') + ->update([ + 'onboarding_completed_at' => $completedAt, + 'updated_at' => $completedAt, + ]); + + if ($updated === 0) { + return false; + } + + $account->forceFill([ + 'onboarding_completed_at' => $completedAt, + 'updated_at' => $completedAt, + ]); + $account->syncOriginalAttribute('onboarding_completed_at'); + $account->syncOriginalAttribute('updated_at'); + + if (PostHogService::isEnabled()) { + $this->postHog->capture( + $user->id, + OnboardingEvent::Completed->value, + account: $account, + ); + } + + // Every stamp path (endpoint, syncProgress, observers) must clear residual + // banners account-wide — not only the explicit complete() action. + OnboardingStatusUpdated::broadcastForAccount($account); + + // Lets the next full Inertia visit (e.g. OAuth popup → router.reload) + // show celebration instead of bouncing straight to calendar. + if (request()->hasSession()) { + request()->session()->put(self::JUST_COMPLETED_SESSION_KEY, true); + } + + return true; + } + + /** + * Sidebar residual banner payload, 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 residual(User $user): array|false + { + $account = $user->account; + + // Cheap gates first: this runs on every full Inertia load, so dismissed / + // completed accounts, members, and self-hosted must never pay for the + // step EXISTS queries in handle() — or even for a subscription lookup. + if (config('trypost.self_hosted') + || $account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + || ! $user->isAccountOwner() + || ! $account->hasAppAccess() + ) { + return false; + } + + $status = $this->handle($user); + + if (! $status['show_residual']) { + return false; + } + + return [ + 'completed' => collect([ + $status['mcp_connected'], + $status['social_connected'], + $status['first_post_created'], + ])->filter()->count(), + 'total' => self::TOTAL_STEPS, + ]; + } + + private function accountHasMcpConnection(?Account $account): bool + { + if ($account === null) { + return false; + } + + return AccessToken::query() + ->whereIn( + 'user_id', + User::query()->select('id')->where('account_id', $account->id), + ) + ->activeMcpOAuth() + ->exists(); + } + + private function accountHasSocialConnection(?Account $account): bool + { + if ($account === null) { + return false; + } + + return Workspace::query() + ->where('account_id', $account->id) + ->whereHas('socialAccounts') + ->exists(); + } + + private function accountHasPost(?Account $account): bool + { + if ($account === null) { + return false; + } + + return Workspace::query() + ->where('account_id', $account->id) + ->whereHas('posts') + ->exists(); + } + + private function captureCompletedStep(User $user, Account $account, string $step, bool $completed): void + { + if (! $completed || ! PostHogService::isEnabled()) { + return; + } + + // Durable once-per-account dedupe (no short TTL re-fire). + if (! Cache::add("onboarding_step:{$account->id}:{$step}", true, now()->addYears(100))) { + return; + } + + $this->postHog->capture( + $user->id, + OnboardingEvent::StepCompleted->value, + ['step' => $step], + $account, + ); + } +} diff --git a/app/Actions/User/CreateUser.php b/app/Actions/User/CreateUser.php index ead5eca04..c03276e0d 100644 --- a/app/Actions/User/CreateUser.php +++ b/app/Actions/User/CreateUser.php @@ -12,6 +12,7 @@ use App\Models\User; use App\Services\PostHogService; use Illuminate\Support\Facades\DB; +use RuntimeException; class CreateUser { @@ -30,8 +31,17 @@ public static function execute(array $data, array $utmParameters = []): User ]; if (! $requiresCardForTrial) { + $trialDays = (int) config('cashier.trial_days'); + + if ($trialDays < 1) { + throw new RuntimeException( + 'CASHIER_TRIAL_DAYS must be at least 1 when REQUIRE_CARD_FOR_TRIAL=false, ' + .'otherwise new accounts would have no trial and no subscription access.' + ); + } + $accountAttributes['plan_id'] = Plan::where('slug', Slug::Workspace)->value('id'); - $accountAttributes['trial_ends_at'] = now()->addDays(config('cashier.trial_days')); + $accountAttributes['trial_ends_at'] = now()->addDays($trialDays); } $account = Account::create($accountAttributes); diff --git a/app/Enums/PostHog/OnboardingEvent.php b/app/Enums/PostHog/OnboardingEvent.php new file mode 100644 index 000000000..ff72b671f --- /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()) + ->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/Goal.php b/app/Enums/User/Goal.php index ced8c382e..836e4bd13 100644 --- a/app/Enums/User/Goal.php +++ b/app/Enums/User/Goal.php @@ -13,9 +13,6 @@ enum Goal: string case GrowAudience = 'grow_audience'; case DriveSales = 'drive_sales'; case ManageClients = 'manage_clients'; - case TeamCollaboration = 'team_collaboration'; - case AutomateApi = 'automate_api'; - case TrackPerformance = 'track_performance'; case JustExploring = 'just_exploring'; case Other = 'other'; } diff --git a/app/Events/OnboardingStatusUpdated.php b/app/Events/OnboardingStatusUpdated.php new file mode 100644 index 000000000..c7e19886b --- /dev/null +++ b/app/Events/OnboardingStatusUpdated.php @@ -0,0 +1,171 @@ +workspaces()->pluck('id') as $workspaceId) { + static::dispatch((string) $workspaceId); + } + } + + /** + * Broadcast to every workspace on the account and sync progress for the actor. + * Use when a step is account-scoped (e.g. MCP OAuth). + * + * Sync/analytics run afterCommit so Cache/PostHog never outlive a rolled-back + * CreatePost (or similar) transaction. + */ + public static function dispatchForAccount(?Account $account, ?User $actor = null): void + { + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + $accountId = $account->id; + $actorId = $actor?->id; + + DB::afterCommit(function () use ($accountId, $actorId): void { + $account = Account::query()->find($accountId); + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + static::syncAndBroadcast($account, $actorId); + }); + } + + /** + * Broadcast only while the workspace account still has active onboarding. + * Steps are account-scoped, so syncing the actor (or the account owner when + * there is no actor, e.g. webhook-driven connects) is enough to stamp. + * + * Sync/analytics run afterCommit so Cache/PostHog never outlive a rolled-back + * CreatePost (or similar) transaction. + */ + public static function dispatchForWorkspace(?string $workspaceId, ?User $actor = null): void + { + if (blank($workspaceId)) { + return; + } + + $account = Workspace::query()->find($workspaceId)?->account; + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + $accountId = $account->id; + $actorId = $actor?->id; + + DB::afterCommit(function () use ($accountId, $actorId): void { + $account = Account::query()->find($accountId); + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + static::syncAndBroadcast($account, $actorId); + }); + } + + /** + * Stamp completion for the actor — falling back to the account owner so + * actor-less flows (Telegram webhook, jobs) still complete — then notify + * every workspace channel exactly once. markCompleted already broadcasts + * when it stamps, so an un-stamped sync is the only case that fans out here. + */ + private static function syncAndBroadcast(Account $account, ?string $actorId): void + { + $actor = $actorId !== null + ? User::query()->with('account')->find($actorId) + : null; + + $syncTarget = $actor !== null && (string) $actor->account_id === (string) $account->id + ? $actor + : $account->owner; + + if ($syncTarget !== null) { + app(ResolveOnboardingStatus::class)->syncProgress($syncTarget); + $account->refresh(); + } + + if ($account->onboarding_completed_at === null + && $account->onboarding_dismissed_at === null + ) { + static::broadcastForAccount($account); + } + } + + 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 e85d36c5d..4a6dd9dab 100644 --- a/app/Http/Controllers/App/BillingController.php +++ b/app/Http/Controllers/App/BillingController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\App; use App\Models\Account; +use App\Support\Billing\CheckoutConversionData; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -18,7 +19,7 @@ class BillingController extends Controller { public function subscribe(): RedirectResponse { - return redirect()->route('app.onboarding'); + return redirect()->route('app.welcome.persona'); } public function processing(Request $request): Response|RedirectResponse @@ -27,59 +28,48 @@ public function processing(Request $request): Response|RedirectResponse return redirect()->route('app.calendar'); } - $account = $request->user()->account; + $user = $request->user(); + $account = $user->account; $sessionId = $request->query('session_id'); - // 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 !== '' - && Cache::add("checkout_tracked:{$sessionId}", true, now()->addDay()); + // First-sight per account, atomically: `Cache::add` returns true only + // for the first request with this session_id, so a back-button/refresh + // can't re-fire `checkout.completed` and a foreign session_id can't + // burn the real buyer's tracking. + $fromCheckout = $account !== null + && $account->stripe_id + && is_string($sessionId) + && $sessionId !== '' + && Cache::add("checkout_tracked:{$account->id}:{$sessionId}", true, now()->addDay()); + + // The purchase pixel only fires with a verified conversion (see + // Processing.vue), so the session must belong to this account's Stripe + // customer. A transient Stripe failure loses the pixel rather than + // firing it unverified. + $conversion = null; + + if ($fromCheckout) { + try { + $session = $account->stripe()->checkout->sessions->retrieve($sessionId); + + $conversion = CheckoutConversionData::fromSession($session, (string) $account->stripe_id); + } catch (Throwable) { + // Verified-or-nothing: no conversion, no purchase event. + } + } return Inertia::render('billing/Processing', [ 'subscriptionActive' => $account && $account->subscribed(Account::SUBSCRIPTION_NAME), 'fromCheckout' => $fromCheckout, - 'persona' => $request->user()->persona?->value, - 'conversion' => $fromCheckout && $account?->stripe_id - ? fn () => $this->buildConversionData($account, $sessionId) - : null, + 'redirectToOnboarding' => $account !== null + && $user->isAccountOwner() + && $account->onboarding_completed_at === null + && $account->onboarding_dismissed_at === null, + 'persona' => $user->persona?->value, + 'conversion' => $conversion, ]); } - /** - * @return array{value: float, currency: string, transaction_id: string}|null - */ - private function buildConversionData(Account $account, string $sessionId): ?array - { - try { - $session = $account->stripe()->checkout->sessions->retrieve( - $sessionId, - ['expand' => ['line_items.data.price']], - ); - } catch (Throwable) { - return null; - } - - if (data_get($session, 'customer') !== $account->stripe_id) { - return null; - } - - $unitAmount = data_get($session, 'line_items.data.0.price.unit_amount'); - $currency = data_get($session, 'line_items.data.0.price.currency'); - $transactionId = data_get($session, 'id'); - - if (! is_int($unitAmount) || ! is_string($currency) || ! is_string($transactionId)) { - return null; - } - - return [ - 'value' => $unitAmount / 100, - 'currency' => strtoupper($currency), - 'transaction_id' => $transactionId, - ]; - } - public function index(Request $request): Response|RedirectResponse { if (config('trypost.self_hosted')) { diff --git a/app/Http/Controllers/App/McpSettingsController.php b/app/Http/Controllers/App/McpSettingsController.php new file mode 100644 index 000000000..8293a636a --- /dev/null +++ b/app/Http/Controllers/App/McpSettingsController.php @@ -0,0 +1,108 @@ +user()->currentWorkspace; + + if (! $workspace) { + return redirect()->route('app.workspaces.create'); + } + + $this->authorize('view', $workspace); + + return Inertia::render('settings/workspace/Mcp', [ + 'mcpUrl' => route('mcp.trypost'), + 'connectedClients' => $this->connectedClients($workspace->account_id, $request->user()), + ]); + } + + public function disconnect(Request $request, string $client): RedirectResponse + { + $workspace = $request->user()->currentWorkspace; + + if (! $workspace) { + return redirect()->route('app.workspaces.create'); + } + + $this->authorize('view', $workspace); + + $user = $request->user(); + + $tokens = AccessToken::query() + ->where('user_id', $user->id) + ->where('client_id', $client) + ->activeMcpOAuth() + ->get(); + + if ($tokens->isEmpty()) { + return back(); + } + + DB::transaction(function () use ($tokens): void { + $tokenIds = $tokens->pluck('id'); + + DB::table('oauth_refresh_tokens')->whereIn('access_token_id', $tokenIds)->update(['revoked' => true]); + + $tokens->each(function (AccessToken $token): void { + $token->forceFill(['revoked' => true])->saveQuietly(); + }); + }); + + OnboardingStatusUpdated::dispatchForAccount($user->account, $user); + + return back()->with('flash.success', __('mcp.disconnected')); + } + + /** + * Active OAuth MCP grants across the account (any teammate), excluding + * personal access API tokens. + * + * @return array + */ + private function connectedClients(string $accountId, User $currentUser): array + { + $tokens = AccessToken::query() + ->whereIn('user_id', User::query()->select('id')->where('account_id', $accountId)) + ->activeMcpOAuth() + ->with('client') + ->get(); + + $users = User::query() + ->whereIn('id', $tokens->pluck('user_id')->unique()->filter()->all()) + ->get() + ->keyBy('id'); + + return $tokens + ->groupBy(fn (AccessToken $token): string => "{$token->client_id}:{$token->user_id}") + ->map(function ($grouped) use ($currentUser, $users): array { + $first = $grouped->first(); + $owner = $users->get($first->user_id); + + return [ + 'client_id' => $first->client_id, + 'name' => $first->client->name, + 'user_id' => (string) $first->user_id, + 'user_name' => (string) ($owner?->name ?? ''), + 'can_disconnect' => $first->user_id === $currentUser->id, + 'last_used_at' => $grouped->max('last_used_at'), + ]; + }) + ->values() + ->all(); + } +} diff --git a/app/Http/Controllers/App/OnboardingController.php b/app/Http/Controllers/App/OnboardingController.php index a9628a224..9d487a994 100644 --- a/app/Http/Controllers/App/OnboardingController.php +++ b/app/Http/Controllers/App/OnboardingController.php @@ -4,18 +4,12 @@ namespace App\Http\Controllers\App; -use App\Actions\Billing\StartSubscriptionCheckout; -use App\Enums\Plan\Slug; +use App\Actions\Onboarding\ResolveOnboardingStatus; +use App\Enums\PostHog\OnboardingEvent; use App\Enums\SocialAccount\Platform as SocialPlatform; -use App\Enums\User\Goal; -use App\Enums\User\Persona; -use App\Enums\User\ReferralSource; -use App\Http\Requests\App\Onboarding\StoreOnboardingGoalsRequest; -use App\Http\Requests\App\Onboarding\StoreOnboardingReferralSourceRequest; -use App\Http\Requests\App\Onboarding\StoreOnboardingRequest; +use App\Events\OnboardingStatusUpdated; use App\Http\Resources\App\SocialAccountResource; use App\Models\Account; -use App\Models\Plan; use App\Services\PostHogService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -25,233 +19,164 @@ class OnboardingController extends Controller { + public function __construct( + private readonly ResolveOnboardingStatus $resolveOnboardingStatus, + private readonly PostHogService $postHog, + ) {} + public function index(Request $request): Response|RedirectResponse { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); + if ($redirect = $this->redirectIfSelfHosted()) { + return $redirect; } $user = $request->user(); + $workspace = $user->currentWorkspace; + // Capture before syncProgress — same-request auto-stamp still shows celebration. + $wasAlreadyComplete = $user->account?->onboarding_completed_at !== null; + $status = $this->resolveOnboardingStatus->syncProgress($user); - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { + // Skip is always terminal (including Echo partial reloads). + if ($status['dismissed_at'] !== null) { return redirect()->route('app.calendar'); } - return Inertia::render('onboarding/Index', [ - 'personas' => array_map(fn (Persona $persona): string => $persona->value, Persona::cases()), - 'selected' => $user->persona?->value, - ]); - } + // Completed revisits leave — keep celebration for Echo/poll partials and + // for the immediate full reload after OAuth stamps completion. + // The just-completed flag is session put (not flash) so partials never + // age it out; only full visits pull/consume it. + $isPartial = $request->hasHeader('X-Inertia-Partial-Component'); + $justCompleted = ! $isPartial + && (bool) $request->session()->pull(ResolveOnboardingStatus::JUST_COMPLETED_SESSION_KEY); - public function store(StoreOnboardingRequest $request, PostHogService $postHog): RedirectResponse - { - if (config('trypost.self_hosted')) { + if ($wasAlreadyComplete && ! $justCompleted && ! $isPartial) { return redirect()->route('app.calendar'); } - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); + if (! $isPartial && ! $wasAlreadyComplete) { + $this->postHog->capture( + $user->id, + OnboardingEvent::Viewed->value, + account: $user->account, + ); } - $persona = (string) $request->validated('persona'); - - $user->update(['persona' => $persona]); + $accounts = SocialAccountResource::collection( + $workspace->socialAccounts()->orderBy('id')->get(), + )->resolve(); - $postHog->identify($user->id, [ - 'persona' => $persona, + return Inertia::render('onboarding/Index', [ + 'status' => $status, + 'canDismiss' => $user->isAccountOwner(), + 'mcpUrl' => route('mcp.trypost'), + 'samplePrompt' => __('onboarding.first_post.sample_prompt'), + 'platforms' => SocialPlatform::connectableOptions(), + 'accounts' => $accounts, + // The step is account-scoped but the grid is workspace-local — tell + // the user when the check comes from a sibling workspace. + 'socialConnectedElsewhere' => $status['social_connected'] && $accounts === [], ]); - - return redirect()->route('app.onboarding.goals'); } - public function goals(Request $request): Response|RedirectResponse + public function dismiss(Request $request): RedirectResponse { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); + if ($redirect = $this->redirectIfSelfHosted()) { + return $redirect; } $user = $request->user(); - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } + abort_unless($user->isAccountOwner(), SymfonyResponse::HTTP_FORBIDDEN); - return Inertia::render('onboarding/Goals', [ - 'goals' => array_map(fn (Goal $goal): string => $goal->value, Goal::cases()), - 'selected' => $user->goals ?? [], - ]); - } - - public function storeGoals(StoreOnboardingGoalsRequest $request, PostHogService $postHog): RedirectResponse - { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); + $account = $user->account; - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { + if ( + $account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { return redirect()->route('app.calendar'); } - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - $goals = array_values($request->validated('goals')); + $dismissedAt = now(); - $user->update(['goals' => $goals]); + $updated = Account::query() + ->whereKey($account->id) + ->whereNull('onboarding_completed_at') + ->whereNull('onboarding_dismissed_at') + ->update([ + 'onboarding_dismissed_at' => $dismissedAt, + 'updated_at' => $dismissedAt, + ]); - $postHog->identify($user->id, [ - 'goals' => $goals, - ]); - - return redirect()->route('app.onboarding.referral-source'); - } - - public function referralSource(Request $request): Response|RedirectResponse - { - if (config('trypost.self_hosted')) { + if ($updated === 0) { return redirect()->route('app.calendar'); } - $user = $request->user(); + $account->forceFill(['onboarding_dismissed_at' => $dismissedAt]); + $account->syncOriginalAttribute('onboarding_dismissed_at'); - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } + $this->postHog->capture( + $user->id, + OnboardingEvent::Skipped->value, + account: $account, + ); - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } + // Refresh residual banners on other tabs/devices immediately. + OnboardingStatusUpdated::broadcastForAccount($account); - if (! $user->goals) { - return redirect()->route('app.onboarding.goals'); + // Sidebar residual dismiss stays on the current page; the onboarding + // page Skip leaves for the calendar. + if ($request->boolean('stay')) { + return redirect()->back(); } - return Inertia::render('onboarding/ReferralSource', [ - 'sources' => array_map(fn (ReferralSource $source): string => $source->value, ReferralSource::cases()), - 'selected' => $user->referral_source?->value, - ]); + return redirect()->route('app.calendar'); } - public function storeReferralSource(StoreOnboardingReferralSourceRequest $request, PostHogService $postHog): RedirectResponse + public function complete(Request $request): RedirectResponse { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); + if ($redirect = $this->redirectIfSelfHosted()) { + return $redirect; } $user = $request->user(); + $account = $user->account; - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { + // Already stamped (e.g. observer / syncProgress auto-complete) — just leave. + if ($account?->onboarding_completed_at !== null) { return redirect()->route('app.calendar'); } - if (! $user->persona) { - return redirect()->route('app.onboarding'); - } - - if (! $user->goals) { - return redirect()->route('app.onboarding.goals'); - } - - $referralSource = (string) $request->validated('referral_source'); - - $user->update(['referral_source' => $referralSource]); - - $postHog->identify($user->id, [ - 'referral_source' => $referralSource, - ]); - - return redirect()->route('app.onboarding.connect'); - } - - public function connect(Request $request): Response|RedirectResponse - { - if (config('trypost.self_hosted')) { + // Skip must stay terminal: never let Continue re-open completion after dismiss. + if ($account?->onboarding_dismissed_at !== null) { return redirect()->route('app.calendar'); } - $user = $request->user(); - - if ($user->account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } + // Any teammate who finishes activation may stamp — observers/syncProgress + // already do the same. Dismiss remains owner-only. Steps are account-scoped. + $status = $this->resolveOnboardingStatus->handle($user); - if (! $user->persona) { + if (! $status['all_complete']) { return redirect()->route('app.onboarding'); } - if (! $user->goals) { - return redirect()->route('app.onboarding.goals'); - } - - if (! $user->referral_source) { - return redirect()->route('app.onboarding.referral-source'); - } - - $workspace = $user->currentWorkspace; + // markCompleted broadcasts account-wide so residual banners clear immediately. + $this->resolveOnboardingStatus->markCompleted($user); - if (! $workspace) { - return redirect()->route('app.workspaces.create'); - } + // The explicit Continue already showed the ready state — the celebration + // flag must not linger and resurface on a later visit. + $request->session()->forget(ResolveOnboardingStatus::JUST_COMPLETED_SESSION_KEY); - $accounts = $workspace->socialAccounts()->orderBy('id')->get(); - - $platforms = collect(SocialPlatform::cases()) - ->filter(fn (SocialPlatform $platform): bool => $platform->isConnectable()) - ->map(fn (SocialPlatform $platform): array => [ - 'value' => $platform->value, - 'label' => $platform->label(), - 'color' => $platform->color(), - 'network' => $platform->network(), - ])->values(); - - $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); - - return Inertia::render('onboarding/Connect', [ - 'platforms' => $platforms, - 'accounts' => SocialAccountResource::collection($accounts)->resolve(), - 'plan' => [ - 'name' => $plan->name, - 'interval' => 'monthly', - ], - ]); + return redirect()->route('app.calendar'); } - public function checkout(Request $request, StartSubscriptionCheckout $checkout): SymfonyResponse|RedirectResponse + private function redirectIfSelfHosted(): ?RedirectResponse { - if (config('trypost.self_hosted')) { - return redirect()->route('app.calendar'); - } - - $user = $request->user(); - $account = $user->account; - - if ($account?->subscribed(Account::SUBSCRIPTION_NAME)) { - return redirect()->route('app.calendar'); - } - - $workspace = $user->currentWorkspace; - - if (! $workspace || ! $workspace->socialAccounts()->exists()) { - return redirect()->route('app.onboarding.connect') - ->with('flash.banner', __('onboarding.connect.must_connect')) - ->with('flash.bannerStyle', 'danger'); + if (! config('trypost.self_hosted')) { + return null; } - $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); - - return $checkout->redirect( - $account, - (string) $plan->stripe_monthly_price_id, - route('app.onboarding.connect'), - ); + return redirect()->route('app.calendar'); } } diff --git a/app/Http/Controllers/App/WelcomeController.php b/app/Http/Controllers/App/WelcomeController.php new file mode 100644 index 000000000..5c6438cc8 --- /dev/null +++ b/app/Http/Controllers/App/WelcomeController.php @@ -0,0 +1,247 @@ +redirectIfUnavailable($request)) { + return $redirect; + } + + return Inertia::render('welcome/Persona', [ + 'personas' => array_map(fn (Persona $persona): string => $persona->value, Persona::cases()), + 'selected' => $request->user()->persona?->value, + ]); + } + + public function storePersona(StoreWelcomePersonaRequest $request, PostHogService $postHog): RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + $persona = (string) $request->validated('persona'); + + $user->update(['persona' => $persona]); + + $postHog->identify($user->id, [ + 'persona' => $persona, + ]); + $postHog->capture( + $user->id, + WelcomeEvent::PersonaSaved->value, + ['persona' => $persona], + $user->account, + ); + + return redirect()->route('app.welcome.goals'); + } + + public function goals(Request $request): Response|RedirectResponse + { + if ($redirect = $this->redirectIfStepIncomplete($request)) { + return $redirect; + } + + $user = $request->user(); + + return Inertia::render('welcome/Goals', [ + 'goals' => array_map(fn (Goal $goal): string => $goal->value, Goal::cases()), + 'selected' => $user->goals ?? [], + ]); + } + + public function storeGoals(StoreWelcomeGoalsRequest $request, PostHogService $postHog): RedirectResponse + { + if ($redirect = $this->redirectIfStepIncomplete($request)) { + return $redirect; + } + + $user = $request->user(); + $goals = array_values($request->validated('goals')); + + $user->update(['goals' => $goals]); + + $postHog->identify($user->id, [ + 'goals' => $goals, + ]); + $postHog->capture( + $user->id, + WelcomeEvent::GoalsSaved->value, + ['goals' => $goals], + $user->account, + ); + + return redirect()->route('app.welcome.referral-source'); + } + + public function referralSource(Request $request): Response|RedirectResponse + { + if ($redirect = $this->redirectIfStepIncomplete($request, requireGoals: true)) { + return $redirect; + } + + $user = $request->user(); + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + $account = $user->account; + + return Inertia::render('welcome/ReferralSource', [ + 'sources' => array_map(fn (ReferralSource $source): string => $source->value, ReferralSource::cases()), + 'selected' => $user->referral_source?->value, + 'canCheckout' => $user->isAccountOwner(), + 'plan' => [ + 'name' => $plan->name, + 'interval' => 'monthly', + ], + 'trialDays' => $account !== null + ? ConfigureSubscriptionCheckout::checkoutTrialDays($account) + : 0, + ]); + } + + public function storeReferralSource( + StoreWelcomeReferralSourceRequest $request, + StartSubscriptionCheckout $checkout, + PostHogService $postHog, + ): SymfonyResponse|RedirectResponse { + if ($redirect = $this->redirectIfStepIncomplete($request, requireGoals: true)) { + return $redirect; + } + + $user = $request->user(); + + abort_unless($user->isAccountOwner(), SymfonyResponse::HTTP_FORBIDDEN); + + $referralSource = (string) $request->validated('referral_source'); + + $user->update(['referral_source' => $referralSource]); + + $postHog->identify($user->id, [ + 'referral_source' => $referralSource, + ]); + $postHog->capture( + $user->id, + WelcomeEvent::ReferralSaved->value, + ['referral_source' => $referralSource], + $user->account, + ); + + $plan = Plan::where('slug', Slug::Workspace)->firstOrFail(); + $priceId = $plan->stripe_monthly_price_id; + + abort_if($priceId === null, SymfonyResponse::HTTP_INTERNAL_SERVER_ERROR, 'Monthly price is not configured.'); + + $response = $checkout->redirect( + $user->account, + $priceId, + route('app.welcome.referral-source'), + ); + + $postHog->capture( + $user->id, + WelcomeEvent::CheckoutStarted->value, + [ + 'plan_name' => $plan->name, + 'interval' => 'monthly', + ], + $user->account, + ); + + return $response; + } + + public function subscriptionRequired(Request $request): Response|RedirectResponse + { + if (config('trypost.self_hosted')) { + return redirect()->route('app.calendar'); + } + + $user = $request->user(); + + if ($user->account?->hasAppAccess()) { + $status = $this->resolveOnboardingStatus->handle($user); + + return redirect()->route($status['show_residual'] ? 'app.onboarding' : 'app.calendar'); + } + + if ($user->isAccountOwner()) { + return redirect()->route('app.welcome.persona'); + } + + return Inertia::render('welcome/SubscriptionRequired', [ + 'ownerName' => $user->account?->owner?->name, + ]); + } + + private function redirectIfStepIncomplete(Request $request, bool $requireGoals = false): ?RedirectResponse + { + if ($redirect = $this->redirectIfUnavailable($request)) { + return $redirect; + } + + $user = $request->user(); + + if (! $user->persona) { + return redirect()->route('app.welcome.persona'); + } + + if ($requireGoals && ! $user->goals) { + return redirect()->route('app.welcome.goals'); + } + + return null; + } + + private function redirectIfUnavailable(Request $request): ?RedirectResponse + { + if (config('trypost.self_hosted')) { + return redirect()->route('app.calendar'); + } + + $user = $request->user(); + + // Match EnsureAccountReady — generic-trial (no-card) users already have + // app access and must not be sent through Stripe checkout again. + if ($user->account?->hasAppAccess()) { + $status = $this->resolveOnboardingStatus->handle($user); + + return redirect()->route($status['show_residual'] ? 'app.onboarding' : 'app.calendar'); + } + + // Members can't check out — hold them on a dedicated screen instead of + // walking an ICP flow they can never finish. + if (! $user->isAccountOwner()) { + return redirect()->route('app.welcome.subscription-required'); + } + + return null; + } +} diff --git a/app/Http/Controllers/App/WorkspaceController.php b/app/Http/Controllers/App/WorkspaceController.php index 4e5ff2121..869a346ed 100644 --- a/app/Http/Controllers/App/WorkspaceController.php +++ b/app/Http/Controllers/App/WorkspaceController.php @@ -86,7 +86,7 @@ public function create(Request $request): Response|RedirectResponse * Block creating a paid additional workspace without an active subscription. * Guards both the form (`create`) and the write (`store`) so a direct POST * can't bootstrap a second billable workspace — which would also inflate the - * checkout quantity past the fixed first-month coupon. + * Stripe Checkout seat quantity before the owner has paid. */ private function denyAdditionalWorkspaceWithoutSubscription(User $user): ?RedirectResponse { diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index ec2ed9788..f328b87c3 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -38,14 +38,7 @@ 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(); + $platforms = SocialPlatform::connectableOptions(); return Inertia::render('accounts/Index', [ 'workspace' => $workspace, diff --git a/app/Http/Middleware/Api/LoadWorkspaceFromToken.php b/app/Http/Middleware/Api/LoadWorkspaceFromToken.php index dd9743646..4b7c5c555 100644 --- a/app/Http/Middleware/Api/LoadWorkspaceFromToken.php +++ b/app/Http/Middleware/Api/LoadWorkspaceFromToken.php @@ -31,7 +31,10 @@ public function handle(Request $request, Closure $next): Response return response()->json(['message' => 'No workspace selected.'], Response::HTTP_UNAUTHORIZED); } - if (! config('trypost.self_hosted') && ! $workspace->account?->hasActiveSubscription()) { + // Match web access (EnsureAccountReady): Stripe subscription OR generic + // no-card trial. MCP onboarding is a first-class checklist step for + // those accounts — requiring subscribed() alone returned 402 after OAuth. + if (! config('trypost.self_hosted') && ! $workspace->account?->hasAppAccess()) { return response()->json(['message' => 'Active subscription required.'], Response::HTTP_PAYMENT_REQUIRED); } diff --git a/app/Http/Middleware/App/EnsureAccountReady.php b/app/Http/Middleware/App/EnsureAccountReady.php index 7e15bfe6c..9e41604bd 100644 --- a/app/Http/Middleware/App/EnsureAccountReady.php +++ b/app/Http/Middleware/App/EnsureAccountReady.php @@ -4,7 +4,6 @@ namespace App\Http\Middleware\App; -use App\Models\Account; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; @@ -24,14 +23,15 @@ public function handle(Request $request, Closure $next): Response if (! config('trypost.self_hosted')) { $account = $user->account; - $requiresCardForTrial = (bool) config('trypost.billing.require_card_for_trial', true); - $hasAccess = $account && ( - $account->subscribed(Account::SUBSCRIPTION_NAME) - || (! $requiresCardForTrial && $account->isOnTrial()) - ); - - if (! $hasAccess) { - return redirect()->route('app.onboarding'); + + if (! $account?->hasAppAccess()) { + // Members can't finish Welcome checkout — send them straight to + // the hold screen instead of hopping through persona first. + if (! $user->isAccountOwner()) { + return redirect()->route('app.welcome.subscription-required'); + } + + return redirect()->route('app.welcome.persona'); } } diff --git a/app/Http/Middleware/App/HandleInertiaRequests.php b/app/Http/Middleware/App/HandleInertiaRequests.php index 167185be8..563b337c7 100644 --- a/app/Http/Middleware/App/HandleInertiaRequests.php +++ b/app/Http/Middleware/App/HandleInertiaRequests.php @@ -4,6 +4,7 @@ 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; @@ -48,6 +49,9 @@ public function share(Request $request): array ], 'usage' => $account && ! $isSelfHosted ? $account->usage() : null, 'features' => $account && ! $isSelfHosted ? $account->featureLimits() : null, + 'onboardingResidual' => fn (): array|false => $user + ? app(ResolveOnboardingStatus::class)->residual($user) + : false, 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'flash' => $request->session()->get('flash', []), 'applicationUrl' => config('app.url'), diff --git a/app/Http/Requests/App/Onboarding/StoreOnboardingGoalsRequest.php b/app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php similarity index 81% rename from app/Http/Requests/App/Onboarding/StoreOnboardingGoalsRequest.php rename to app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php index e127eab3c..987fa2c4e 100644 --- a/app/Http/Requests/App/Onboarding/StoreOnboardingGoalsRequest.php +++ b/app/Http/Requests/App/Welcome/StoreWelcomeGoalsRequest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace App\Http\Requests\App\Onboarding; +namespace App\Http\Requests\App\Welcome; use App\Enums\User\Goal; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class StoreOnboardingGoalsRequest extends FormRequest +class StoreWelcomeGoalsRequest extends FormRequest { public function authorize(): bool { diff --git a/app/Http/Requests/App/Onboarding/StoreOnboardingRequest.php b/app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php similarity index 81% rename from app/Http/Requests/App/Onboarding/StoreOnboardingRequest.php rename to app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php index ee0d74283..c22f29fc7 100644 --- a/app/Http/Requests/App/Onboarding/StoreOnboardingRequest.php +++ b/app/Http/Requests/App/Welcome/StoreWelcomePersonaRequest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace App\Http\Requests\App\Onboarding; +namespace App\Http\Requests\App\Welcome; use App\Enums\User\Persona; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class StoreOnboardingRequest extends FormRequest +class StoreWelcomePersonaRequest extends FormRequest { public function authorize(): bool { diff --git a/app/Http/Requests/App/Onboarding/StoreOnboardingReferralSourceRequest.php b/app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php similarity index 80% rename from app/Http/Requests/App/Onboarding/StoreOnboardingReferralSourceRequest.php rename to app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php index 47c9a5ae4..960089ec0 100644 --- a/app/Http/Requests/App/Onboarding/StoreOnboardingReferralSourceRequest.php +++ b/app/Http/Requests/App/Welcome/StoreWelcomeReferralSourceRequest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace App\Http\Requests\App\Onboarding; +namespace App\Http\Requests\App\Welcome; use App\Enums\User\ReferralSource; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class StoreOnboardingReferralSourceRequest extends FormRequest +class StoreWelcomeReferralSourceRequest extends FormRequest { public function authorize(): bool { diff --git a/app/Models/AccessToken.php b/app/Models/AccessToken.php index 0f81b83c0..a236072b4 100644 --- a/app/Models/AccessToken.php +++ b/app/Models/AccessToken.php @@ -4,9 +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 { /** @@ -41,4 +45,47 @@ public function workspace(): BelongsTo { return $this->belongsTo(Workspace::class); } + + /** + * Active OAuth grants used by MCP clients (excludes personal access API keys). + * + * @param Builder $query + * @return Builder + */ + public function scopeActiveMcpOAuth(Builder $query): Builder + { + return $query + ->where('revoked', false) + ->where(function (Builder $expires): void { + $expires->whereNull('expires_at') + ->orWhere('expires_at', '>', now()); + }) + ->whereHas( + 'client', + fn (Builder $client): Builder => $client + ->where('revoked', false) + ->whereJsonDoesntContain('grant_types', 'personal_access'), + ); + } + + /** + * Whether this token belongs to an MCP OAuth client (not a personal access + * API key) and is unexpired. Intentionally ignores `revoked`: the observer + * needs a stable answer while a revoke is mid-flight so it can broadcast + * the disconnect. Use the `activeMcpOAuth` scope for "currently usable". + */ + public function isMcpOAuthGrant(): bool + { + $this->loadMissing('client'); + + if ($this->client === null || $this->client->revoked || $this->client->hasGrantType('personal_access')) { + return false; + } + + if ($this->expires_at !== null && $this->expires_at->isPast()) { + return false; + } + + return true; + } } diff --git a/app/Models/Account.php b/app/Models/Account.php index b8e31b00a..e5fc55a92 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -38,10 +38,14 @@ public static function postsCountCacheKey(string $accountId): string 'billing_email', 'plan_id', 'trial_ends_at', + 'onboarding_completed_at', + 'onboarding_dismissed_at', ]; protected $casts = [ 'trial_ends_at' => 'datetime', + 'onboarding_completed_at' => 'datetime', + 'onboarding_dismissed_at' => 'datetime', ]; public function owner(): BelongsTo @@ -78,6 +82,22 @@ public function hasActiveSubscription(): bool return $this->subscribed(self::SUBSCRIPTION_NAME); } + /** + * Whether the account may use the app (active subscription, or a generic + * trial when REQUIRE_CARD_FOR_TRIAL is disabled). + */ + public function hasAppAccess(): bool + { + if (config('trypost.self_hosted')) { + return true; + } + + $requiresCardForTrial = (bool) config('trypost.billing.require_card_for_trial', true); + + return $this->subscribed(self::SUBSCRIPTION_NAME) + || (! $requiresCardForTrial && $this->isOnTrial()); + } + /** * Align the Stripe subscription quantity with the number of workspaces the * account owns. Each workspace is a billed unit. No-op in self-hosted mode diff --git a/app/Observers/AccessTokenObserver.php b/app/Observers/AccessTokenObserver.php new file mode 100644 index 000000000..1325ed250 --- /dev/null +++ b/app/Observers/AccessTokenObserver.php @@ -0,0 +1,47 @@ +revoked) { + return; + } + + $this->broadcastIfMcpOAuth($accessToken); + } + + public function updated(AccessToken $accessToken): void + { + if (! $accessToken->wasChanged('revoked')) { + return; + } + + $this->broadcastIfMcpOAuth($accessToken); + } + + private function broadcastIfMcpOAuth(AccessToken $accessToken): void + { + if (! $accessToken->isMcpOAuthGrant()) { + return; + } + + $user = User::query() + ->with('account') + ->find($accessToken->user_id); + + OnboardingStatusUpdated::dispatchForAccount($user?->account, $user); + } +} diff --git a/app/Observers/PostObserver.php b/app/Observers/PostObserver.php index 1486cfc04..a2aaac66b 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\Post; +use App\Models\User; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class PostObserver @@ -16,6 +19,70 @@ class PostObserver public function created(Post $post): void { DB::afterCommit(fn () => PostCreated::dispatch($post)); + + $account = $post->workspace?->account; + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + // Only the account's first post unlocks the onboarding step — later + // creates would just spam Echo reloads while activation is still open. + $isFirstPost = Post::query() + ->whereIn('workspace_id', $account->workspaces()->select('id')) + ->whereKeyNot($post->id) + ->doesntExist(); + + if ($isFirstPost) { + OnboardingStatusUpdated::dispatchForWorkspace( + $post->workspace_id, + $this->actorFor($post), + ); + } + } + + public function deleted(Post $post): void + { + $account = $post->workspace?->account; + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + // The step only flips when the account's last post disappears. + $accountHasPosts = Post::query() + ->whereIn('workspace_id', $account->workspaces()->select('id')) + ->exists(); + + if (! $accountHasPosts) { + OnboardingStatusUpdated::dispatchForWorkspace( + $post->workspace_id, + $this->actorFor($post), + ); + } + } + + /** + * Prefer the authenticated request user (may carry an in-memory API/MCP + * workspace) over the persisted post author for checklist sync. + */ + private function actorFor(Post $post): ?User + { + $user = Auth::user(); + + if ($user instanceof User + && (string) $user->account_id === (string) $post->workspace?->account_id + ) { + return $user; + } + + return $post->user; } public function saved(Post $post): void diff --git a/app/Observers/SocialAccountObserver.php b/app/Observers/SocialAccountObserver.php index fb41f5971..72816e5a3 100644 --- a/app/Observers/SocialAccountObserver.php +++ b/app/Observers/SocialAccountObserver.php @@ -5,10 +5,13 @@ namespace App\Observers; use App\Enums\SocialAccount\Platform; +use App\Events\OnboardingStatusUpdated; use App\Exceptions\SocialAccount\NetworkAlreadyConnectedException; use App\Jobs\PostHog\SyncAccountUsage; use App\Models\SocialAccount; +use App\Models\User; use App\Services\PostHogService; +use Illuminate\Support\Facades\Auth; class SocialAccountObserver { @@ -44,11 +47,69 @@ public function creating(SocialAccount $socialAccount): void public function created(SocialAccount $socialAccount): void { $this->syncUsage($socialAccount); + + $account = $socialAccount->workspace?->account; + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + // Only the account's first connection unlocks the onboarding step. + $isFirstConnection = SocialAccount::query() + ->whereIn('workspace_id', $account->workspaces()->select('id')) + ->whereKeyNot($socialAccount->id) + ->doesntExist(); + + if ($isFirstConnection) { + OnboardingStatusUpdated::dispatchForWorkspace( + $socialAccount->workspace_id, + $this->actorFor($socialAccount), + ); + } } public function deleted(SocialAccount $socialAccount): void { $this->syncUsage($socialAccount); + + $account = $socialAccount->workspace?->account; + + if ($account === null + || $account->onboarding_completed_at !== null + || $account->onboarding_dismissed_at !== null + ) { + return; + } + + // The step only flips when the account's last connection disappears. + $accountHasConnections = SocialAccount::query() + ->whereIn('workspace_id', $account->workspaces()->select('id')) + ->exists(); + + if (! $accountHasConnections) { + OnboardingStatusUpdated::dispatchForWorkspace( + $socialAccount->workspace_id, + $this->actorFor($socialAccount), + ); + } + } + + private function actorFor(SocialAccount $socialAccount): ?User + { + $user = Auth::user(); + + if (! $user instanceof User) { + return null; + } + + if ((string) $user->account_id !== (string) $socialAccount->workspace?->account_id) { + return null; + } + + return $user; } private function syncUsage(SocialAccount $socialAccount): void diff --git a/app/Policies/AccountPolicy.php b/app/Policies/AccountPolicy.php index b7042f16e..78460f5fd 100644 --- a/app/Policies/AccountPolicy.php +++ b/app/Policies/AccountPolicy.php @@ -32,10 +32,7 @@ public function useAi(User $user, Account $account): Response return Response::allow(); } - $requiresCardForTrial = (bool) config('trypost.billing.require_card_for_trial', true); - - $hasAccess = $account->subscribed(Account::SUBSCRIPTION_NAME) - || (! $requiresCardForTrial && $account->isOnTrial()); + $hasAccess = $account->hasAppAccess(); if (! $hasAccess) { return Response::deny(__('billing.flash.subscription_required')); diff --git a/app/Support/Billing/CashierCheckoutEnv.php b/app/Support/Billing/CashierCheckoutEnv.php new file mode 100644 index 000000000..8925b7f9c --- /dev/null +++ b/app/Support/Billing/CashierCheckoutEnv.php @@ -0,0 +1,39 @@ + $amountTotal / 100, + 'currency' => strtoupper((string) $currency), + 'transaction_id' => (string) $transactionId, + ]; + } +} diff --git a/app/Support/Billing/ConfigureSubscriptionCheckout.php b/app/Support/Billing/ConfigureSubscriptionCheckout.php new file mode 100644 index 000000000..614d8536d --- /dev/null +++ b/app/Support/Billing/ConfigureSubscriptionCheckout.php @@ -0,0 +1,76 @@ + 0) { + $subscription->trialDays($trialDays); + } + + if (CashierCheckoutEnv::allowPromotionCodes(config('cashier.allow_promotion_codes'))) { + $subscription->allowPromotionCodes(); + } + + return $subscription; + } + + /** + * Trial length the checkout will actually apply — the single source of truth + * for both the builder and any UI that advertises the trial. 0 disables the + * trial (immediate charge); 1 is clamped to Stripe's 48-hour minimum. + */ + public static function checkoutTrialDays(Account $account): int + { + $trialDays = (int) config('cashier.trial_days'); + + if ($trialDays <= 0 || ! self::qualifiesForCheckoutTrial($account)) { + return 0; + } + + return max(self::MIN_CHECKOUT_TRIAL_DAYS, $trialDays); + } + + /** + * Checkout trials are for genuinely new card-required signups. Accounts whose + * only prior subscriptions died incomplete (never paid) still qualify; any + * subscription that made it past incomplete, and no-card generic-trial + * installs, skip the trial and keep promotion codes. + */ + public static function qualifiesForCheckoutTrial(Account $account): bool + { + if (! (bool) config('trypost.billing.require_card_for_trial', true)) { + return false; + } + + return ! $account->subscriptions() + ->where('type', Account::SUBSCRIPTION_NAME) + ->whereNotIn('stripe_status', [ + StripeSubscription::STATUS_INCOMPLETE, + StripeSubscription::STATUS_INCOMPLETE_EXPIRED, + ]) + ->exists(); + } +} diff --git a/app/Support/Billing/FirstMonthCheckoutDiscount.php b/app/Support/Billing/FirstMonthCheckoutDiscount.php deleted file mode 100644 index 2fba2131c..000000000 --- a/app/Support/Billing/FirstMonthCheckoutDiscount.php +++ /dev/null @@ -1,66 +0,0 @@ -allowPromotionCodes(); - } - - $couponId = config('cashier.first_month_coupon_id'); - - if (! is_string($couponId) || $couponId === '') { - throw new RuntimeException( - 'STRIPE_FIRST_MONTH_COUPON_ID must be set when REQUIRE_CARD_FOR_TRIAL is enabled, ' - .'otherwise checkout would charge the full price instead of the $1 first month.' - ); - } - - return $subscription->withCoupon($couponId); - } - - /** - * The fixed-amount first-month coupon only applies to a genuinely new - * customer checking out a single workspace: the fixed `amount_off` is only - * correct for a quantity of one, and the $1 offer is for first-time signups - * — not a returning account re-subscribing with workspaces it kept from a - * lapsed subscription. A subscription that never left `incomplete` never - * became real, so a new customer retrying after a failed first attempt - * still qualifies; any started subscription (even canceled) does not. - */ - private static function qualifiesForPaidFirstMonth(Account $account): bool - { - if (! (bool) config('trypost.billing.require_card_for_trial', true)) { - return false; - } - - return $account->workspaces()->count() === 1 - && ! $account->subscriptions() - ->whereNotIn('stripe_status', [ - StripeSubscription::STATUS_INCOMPLETE, - StripeSubscription::STATUS_INCOMPLETE_EXPIRED, - ]) - ->exists(); - } -} diff --git a/config/cashier.php b/config/cashier.php index 523c76b09..045825418 100644 --- a/config/cashier.php +++ b/config/cashier.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Support\Billing\CashierCheckoutEnv; use Laravel\Cashier\Console\WebhookCommand; use Laravel\Cashier\Invoices\DompdfInvoiceRenderer; @@ -131,25 +132,29 @@ | Trial Period |-------------------------------------------------------------------------- | - | The number of days for the trial period. Set to 0 to disable trials. + | Days of Stripe Checkout trial when REQUIRE_CARD_FOR_TRIAL is enabled, and + | the generic no-card trial length when it is disabled. Set to 0 to disable + | Checkout trials (customer is charged immediately at checkout). + | + | Empty / missing env falls back to 8 (an empty string must not become 0). + | Values of 1 are clamped to 2 at checkout — Stripe requires ≥ 48 hours. | */ - 'trial_days' => env('CASHIER_TRIAL_DAYS', 8), + 'trial_days' => CashierCheckoutEnv::trialDays(env('CASHIER_TRIAL_DAYS', 8)), /* |-------------------------------------------------------------------------- - | Paid First Month Coupon + | Allow Promotion Codes |-------------------------------------------------------------------------- | - | Stripe Coupon ID applied at checkout so the first invoice comes out to - | $1 instead of the full monthly price — a real charge validates the - | card up front instead of a $0 trial authorization. Must be an - | `amount_off` coupon with `duration: once`, so it discounts only the - | first invoice and the full price bills automatically afterward. + | Show Stripe Checkout's promotion-code field so customers can redeem + | promotion codes created in the Stripe Dashboard. | */ - 'first_month_coupon_id' => env('STRIPE_FIRST_MONTH_COUPON_ID'), + 'allow_promotion_codes' => CashierCheckoutEnv::allowPromotionCodes( + env('CASHIER_ALLOW_PROMOTION_CODES', true) + ), ]; 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 000000000..f6d2d9169 --- /dev/null +++ b/database/migrations/2026_07_24_160704_add_onboarding_timestamps_to_accounts_table.php @@ -0,0 +1,31 @@ +timestamp('onboarding_completed_at')->nullable()->after('trial_ends_at'); + $table->timestamp('onboarding_dismissed_at')->nullable()->after('onboarding_completed_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('accounts', function (Blueprint $table) { + $table->dropColumn(['onboarding_completed_at', 'onboarding_dismissed_at']); + }); + } +}; 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 000000000..e6a77de78 --- /dev/null +++ b/database/migrations/2026_07_29_183500_backfill_onboarding_dismissed_for_accounts_with_app_access.php @@ -0,0 +1,89 @@ +stampInChunks( + DB::table('accounts') + ->whereNull('onboarding_dismissed_at') + ->whereNull('onboarding_completed_at'), + $now, + ); + + return; + } + + $query = DB::table('accounts') + ->whereNull('onboarding_dismissed_at') + ->whereNull('onboarding_completed_at') + ->where(function (Builder $accounts) use ($now): void { + $accounts->whereExists(function (Builder $subscriptions) use ($now): void { + $subscriptions->selectRaw('1') + ->from('subscriptions') + ->whereColumn('subscriptions.account_id', 'accounts.id') + ->where('subscriptions.type', 'default') + ->where(function (Builder $valid) use ($now): void { + $valid + ->where('subscriptions.ends_at', '>', $now) + ->orWhere('subscriptions.trial_ends_at', '>', $now) + ->orWhere(function (Builder $active) use ($now): void { + $active->where(function (Builder $notEnded) use ($now): void { + $notEnded->whereNull('subscriptions.ends_at') + ->orWhere('subscriptions.ends_at', '>', $now); + })->whereNotIn('subscriptions.stripe_status', [ + 'incomplete', + 'incomplete_expired', + 'unpaid', + ]); + }); + }); + }); + + if (! (bool) config('trypost.billing.require_card_for_trial', true)) { + $accounts->orWhere('accounts.trial_ends_at', '>', $now); + } + }); + + $this->stampInChunks($query, $now); + } + + /** + * Update in id-ordered chunks so a large accounts table is not locked by a + * single statement. chunkById paginates on the id column, so rows stamped + * by earlier chunks falling out of the WHERE is harmless. + */ + private function stampInChunks(Builder $query, CarbonInterface $now): void + { + $query->chunkById(500, function ($accounts) use ($now): void { + DB::table('accounts') + ->whereIn('id', $accounts->pluck('id')) + ->update([ + 'onboarding_dismissed_at' => $now, + 'updated_at' => $now, + ]); + }); + } + + public function down(): void + { + // Data backfill — rows stamped here are indistinguishable from a real + // user skip, so a rollback intentionally keeps them. + } +}; diff --git a/lang/ar/billing.php b/lang/ar/billing.php index d32643acc..715ef2c33 100644 --- a/lang/ar/billing.php +++ b/lang/ar/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'تم إلغاء الدفع', 'cancelled_description' => 'تم إلغاء عملية الدفع. لم تُجرَ أي رسوم.', 'retry' => 'إعادة المحاولة', + 'taking_long' => 'يستغرق هذا وقتًا أطول من المتوقع — انتظر، ما زلنا نجهّز حسابك.', + 'live' => 'مباشر', ], ]; diff --git a/lang/ar/mcp.php b/lang/ar/mcp.php new file mode 100644 index 000000000..7220a00cb --- /dev/null +++ b/lang/ar/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'اربط مساعدي الذكاء الاصطناعي لإنشاء المنشورات وإدارتها بحساب TryPost الخاص بك.', + 'step_add' => 'الصق الاسم أو الرابط أو الإعداد أدناه في تطبيقك. يفتح تسجيل الدخول في المتصفح عند أول اتصال.', + 'name_label' => 'الاسم', + 'url_label' => 'رابط الخادم', + 'config_label' => 'الإعداد', + 'connected_title' => 'التطبيقات المتصلة', + 'connected_description' => 'المساعدون الذين سجّل دخولهم أي شخص على هذا الحساب. يمكنك قطع اتصال تطبيقاتك فقط.', + 'connected_empty' => 'لا يوجد اتصال بعد. استخدم Claude أو ChatGPT أو عميلًا آخر أعلاه.', + 'connected_by' => 'متصل بواسطة :name', + 'disconnect' => 'قطع الاتصال', + 'disconnect_title' => 'قطع اتصال التطبيق', + 'disconnect_confirm' => 'يؤدي هذا إلى تسجيل خروج التطبيق من TryPost. سيحتاج إلى إعادة الاتصال قبل استخدام MCP مجددًا.', + 'disconnected' => 'تم قطع اتصال التطبيق.', + 'copied' => 'تم النسخ', + 'last_used' => 'آخر استخدام', + 'never' => 'أبدًا', + 'documentation_title' => 'التوثيق', + 'documentation_description' => 'أدلة الإعداد لكل عميل، والأدوات المتاحة، وحل المشكلات.', + 'view_docs' => 'عرض التوثيق', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'تطبيقات أخرى', + 'other_clients_description' => 'Cursor وVS Code وClaude Code وأي تطبيق يدعم MCP.', + + 'clients' => [ + 'cursor' => 'أضف TryPost كخادم MCP بعيد في Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'الصق الإعداد أدناه في إعدادات MCP في VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'الصق الإعداد أدناه في إعدادات MCP في Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'يعمل مع أي عميل يقرأ إعداد mcpServers.', + 'other_name' => 'أخرى', + ], +]; diff --git a/lang/ar/onboarding.php b/lang/ar/onboarding.php index 5103f1d30..89e18ea1a 100644 --- a/lang/ar/onboarding.php +++ b/lang/ar/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'مرحبًا بك في TryPost', + 'description' => 'اتبع الخطوات أدناه لمعرفة كيف يعمل TryPost ونشر منشورك الأول.', + 'skip' => 'تخطِّ الآن', + 'continue' => 'المتابعة إلى TryPost', + 'status' => [ + 'complete' => 'مكتمل', + 'todo' => 'مطلوب', ], - 'goals_title' => 'ما هدفك من استخدام TryPost؟', - 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', - 'goals' => [ - 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', - 'ai_content' => 'إنشاء منشورات أسرع بالذكاء الاصطناعي', - 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', - 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', - 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', - 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', - 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', - 'team_collaboration' => 'العمل مع فريقي', - 'automate_api' => 'أتمتة النشر عبر الواجهة البرمجية أو MCP أو الكود', - 'track_performance' => 'معرفة أداء منشوراتي', - 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', - 'other' => 'شيء آخر', + 'mcp' => [ + 'title' => 'اربط مساعد الذكاء الاصطناعي', + 'description' => 'أضِف TryPost كخادم MCP ليتمكّن مساعدك من إنشاء منشورات التواصل وإدارتها نيابةً عنك.', + 'copy_step' => 'انسخ عنوان خادم TryPost', + 'open_step' => 'افتح مساعد الذكاء الاصطناعي', + 'copy' => 'نسخ الرابط', + 'copied' => 'تم نسخ رابط MCP.', + 'connect' => 'الاتصال عبر :client', + 'clients' => [ + 'claude' => 'افتح Settings → Connectors، أضِف موصلًا مخصصًا، ثم الصق الرابط أعلاه.', + 'chatgpt' => 'افتح Settings → Apps & Connectors، أنشئ موصلًا مخصصًا، ثم الصق الرابط أعلاه.', + ], ], - '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/settings.php b/lang/ar/settings.php index 36c2ecad1..d3dc72c88 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -129,6 +129,7 @@ 'brand' => 'العلامة التجارية', 'users' => 'الأعضاء', 'api_keys' => 'مفاتيح API', + 'mcp' => 'MCP', ], 'title' => 'إعدادات مساحة العمل', 'logo_heading' => 'شعار مساحة العمل', diff --git a/lang/ar/sidebar.php b/lang/ar/sidebar.php index a9587e13e..a875ea2b0 100644 --- a/lang/ar/sidebar.php +++ b/lang/ar/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'إنشاء مساحة عمل', 'create_post' => 'إنشاء منشور', 'profile' => 'الملف الشخصي', + 'my_account' => 'حسابي', + 'account_settings' => 'الحساب والفوترة', 'log_out' => 'تسجيل الخروج', 'workspace' => 'مساحة العمل: :name', @@ -30,6 +32,8 @@ 'analytics' => 'التحليلات', 'automations' => 'الأتمتة', 'settings' => 'الإعدادات', + 'onboarding' => 'البدء', + 'onboarding_hint' => 'أكمل الإعداد', 'posts' => [ 'calendar' => 'التقويم', @@ -44,7 +48,9 @@ 'signatures' => 'التوقيعات', 'labels' => 'التسميات', 'assets' => 'الوسائط', + 'settings' => 'الإعدادات', 'api_keys' => 'مفاتيح API', + 'mcp' => 'MCP', ], 'notifications' => 'الإشعارات', diff --git a/lang/ar/welcome.php b/lang/ar/welcome.php new file mode 100644 index 000000000..266dfec52 --- /dev/null +++ b/lang/ar/welcome.php @@ -0,0 +1,57 @@ + 'ما الذي يصفك بشكل أفضل؟', + 'description' => 'اختر الخيار الأقرب وسنخصص تجربتك.', + 'continue' => 'متابعة', + 'checkout_owner_only' => 'اطلب من مالك الحساب إكمال الدفع وبدء الاشتراك.', + 'checkout_trial_note' => 'تجربة مجانية لمدة :days أيام، ثم تُحاسَب على :plan شهريًا. ألغِ في أي وقت.', + 'checkout_plan_note' => 'بعد إتمام الدفع، تُحاسَب على :plan شهريًا.', + 'subscription_required_title' => 'في انتظار مالك الحساب', + 'subscription_required_description' => 'هذا الحساب لا يملك اشتراكًا نشطًا بعد. اطلب من مالك الحساب إكمال الدفع — ستحصل على وصول كامل فور تفعيل الاشتراك.', + 'subscription_required_owner' => 'مالك حسابك هو :name.', + 'progress' => 'تقدم الترحيب', + 'go_to_step' => 'الانتقال إلى الخطوة :step', + 'personas' => [ + 'creator' => 'صانع محتوى', + 'freelancer' => 'مستقل', + 'developer' => 'مطوّر', + 'startup' => 'شركة ناشئة', + 'agency' => 'وكالة', + 'small_business' => 'نشاط تجاري صغير', + 'marketer' => 'مسوّق', + 'online_store' => 'متجر إلكتروني', + 'other' => 'أخرى', + ], + 'goals_title' => 'ما هدفك؟', + 'goals_description' => 'اختر كل ما يناسبك وسنقوم بإعداد TryPost من أجلك.', + 'goals' => [ + 'save_time' => 'توفير الوقت بالنشر في كل مكان دفعة واحدة', + 'ai_content' => 'إنشاء منشورات أسرع بالذكاء الاصطناعي', + 'plan_calendar' => 'التخطيط لمنشوراتي على التقويم', + 'stay_on_brand' => 'الحفاظ على اتساق كل منشور مع العلامة التجارية', + 'grow_audience' => 'تنمية جمهوري وزيادة التفاعل', + 'drive_sales' => 'الحصول على المزيد من الزيارات والمبيعات', + 'manage_clients' => 'إدارة عدة علامات تجارية أو عملاء', + 'just_exploring' => 'مجرد استكشاف في الوقت الحالي', + 'other' => 'شيء آخر', + ], + 'referral_source_title' => 'كيف وجدتنا؟', + 'referral_source_description' => 'يساعدنا هذا على فهم كيفية اكتشاف الأشخاص لـ TryPost.', + 'referral_source' => [ + 'google' => 'Google أو البحث', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram أو Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'مساعد ذكاء اصطناعي (ChatGPT، Claude…)', + 'friend' => 'صديق أو زميل', + 'blog' => 'مدونة أو نشرة إخبارية أو مقال', + 'other' => 'شيء آخر', + ], +]; diff --git a/lang/de/billing.php b/lang/de/billing.php index b977e9f3c..316712286 100644 --- a/lang/de/billing.php +++ b/lang/de/billing.php @@ -74,5 +74,7 @@ 'cancelled_title' => 'Bezahlvorgang abgebrochen', 'cancelled_description' => 'Dein Bezahlvorgang wurde abgebrochen. Es wurden keine Kosten berechnet.', 'retry' => 'Erneut versuchen', + 'taking_long' => 'Das dauert länger als erwartet — bitte warten, die Einrichtung läuft noch.', + 'live' => 'Live', ], ]; diff --git a/lang/de/mcp.php b/lang/de/mcp.php new file mode 100644 index 000000000..958b89989 --- /dev/null +++ b/lang/de/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Verbinde KI-Assistenten, damit sie Beiträge mit deinem TryPost-Konto erstellen und verwalten können.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Verbundene Apps', + 'connected_description' => 'Assistenten, die jemand in diesem Konto angemeldet hat. Du kannst nur deine eigenen trennen.', + 'connected_empty' => 'Noch nichts verbunden. Nutze Claude, ChatGPT oder einen anderen Client oben.', + 'connected_by' => 'Verbunden von :name', + 'disconnect' => 'Trennen', + 'disconnect_title' => 'App trennen', + 'disconnect_confirm' => 'Dadurch wird die App von TryPost abgemeldet. Sie muss sich neu verbinden, bevor sie MCP wieder nutzen kann.', + 'disconnected' => 'App getrennt.', + 'copied' => 'Kopiert', + 'last_used' => 'Zuletzt verwendet', + 'never' => 'Nie', + 'documentation_title' => 'Dokumentation', + 'documentation_description' => 'Einrichtungsguides pro Client, verfügbare Tools und Fehlerhilfe.', + 'view_docs' => 'Dokumentation ansehen', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Andere Apps', + 'other_clients_description' => 'Cursor, VS Code, Claude Code und alles andere, das MCP spricht.', + + 'clients' => [ + 'cursor' => 'Füge TryPost in Cursor als Remote-MCP-Server hinzu.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Füge die Konfiguration unten in die MCP-Einstellungen von VS Code ein.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Füge die Konfiguration unten in die MCP-Einstellungen von Claude Code ein.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Funktioniert mit jedem Client, der eine mcpServers-Config liest.', + 'other_name' => 'Andere', + ], +]; diff --git a/lang/de/onboarding.php b/lang/de/onboarding.php index 9ccf8ad72..2a3f377a3 100644 --- a/lang/de/onboarding.php +++ b/lang/de/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Willkommen bei TryPost', + 'description' => 'Folge den Schritten unten, um zu sehen, wie TryPost funktioniert, und deinen ersten Beitrag zu veröffentlichen.', + 'skip' => 'Vorerst überspringen', + 'continue' => 'Weiter zu TryPost', + 'status' => [ + 'complete' => 'Erledigt', + 'todo' => 'Offen', ], - '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 schneller mit KI 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', - 'team_collaboration' => 'Mit meinem Team arbeiten', - 'automate_api' => 'Das Posten per API, MCP oder Code automatisieren', - 'track_performance' => 'Sehen, wie meine Beiträge performen', - '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.', + 'copy_step' => 'Kopiere deine TryPost-Server-URL', + 'open_step' => 'Öffne deinen KI-Assistenten', + 'copy' => 'URL kopieren', + 'copied' => 'MCP-URL kopiert.', + 'connect' => 'Mit :client verbinden', + 'clients' => [ + 'claude' => 'Öffne Settings → Connectors, füge einen benutzerdefinierten Connector hinzu und füge die URL oben ein.', + 'chatgpt' => 'Öffne Settings → Apps & Connectors, erstelle einen benutzerdefinierten Connector und füge die URL oben ein.', + ], ], - '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/settings.php b/lang/de/settings.php index 9ef23eb1b..e1bfc9991 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -131,6 +131,7 @@ 'brand' => 'Marke', 'users' => 'Mitglieder', 'api_keys' => 'API-Keys', + 'mcp' => 'MCP', ], 'title' => 'Workspace-Einstellungen', 'logo_heading' => 'Workspace-Logo', diff --git a/lang/de/sidebar.php b/lang/de/sidebar.php index c9a593f97..0fddfdde4 100644 --- a/lang/de/sidebar.php +++ b/lang/de/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Workspace erstellen', 'create_post' => 'Beitrag erstellen', 'profile' => 'Profil', + 'my_account' => 'Mein Konto', + 'account_settings' => 'Konto & Abrechnung', 'log_out' => 'Abmelden', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automatisierungen', 'settings' => 'Einstellungen', + 'onboarding' => 'Erste Schritte', + 'onboarding_hint' => 'Einrichtung abschließen', 'posts' => [ 'calendar' => 'Kalender', @@ -44,7 +48,9 @@ 'signatures' => 'Signaturen', 'labels' => 'Labels', 'assets' => 'Assets', + 'settings' => 'Einstellungen', 'api_keys' => 'API-Keys', + 'mcp' => 'MCP', ], 'notifications' => 'Benachrichtigungen', diff --git a/lang/de/welcome.php b/lang/de/welcome.php new file mode 100644 index 000000000..3f14229e9 --- /dev/null +++ b/lang/de/welcome.php @@ -0,0 +1,57 @@ + 'Was beschreibt dich am besten?', + 'description' => 'Wähle die passende Option, und wir passen dein Erlebnis an.', + 'continue' => 'Weiter', + 'checkout_owner_only' => 'Bitte den Kontoinhaber, den Checkout abzuschließen und das Abo zu starten.', + 'checkout_trial_note' => ':days Tage kostenlos testen, danach wird :plan monatlich abgerechnet. Jederzeit kündbar.', + 'checkout_plan_note' => 'Nach dem Checkout wird :plan monatlich abgerechnet.', + 'subscription_required_title' => 'Warten auf den Kontoinhaber', + 'subscription_required_description' => 'Dieses Konto hat noch kein aktives Abo. Bitte den Kontoinhaber, den Checkout abzuschließen — du erhältst vollen Zugriff, sobald es aktiv ist.', + 'subscription_required_owner' => 'Der Kontoinhaber ist :name.', + 'progress' => 'Willkommensfortschritt', + 'go_to_step' => 'Zu Schritt :step gehen', + 'personas' => [ + 'creator' => 'Content Creator', + 'freelancer' => 'Freelancer', + 'developer' => 'Entwickler', + 'startup' => 'Startup', + 'agency' => 'Agentur', + 'small_business' => 'Kleinunternehmen', + 'marketer' => 'Marketer', + 'online_store' => 'Onlineshop', + 'other' => 'Sonstiges', + ], + 'goals_title' => 'Was ist dein Ziel?', + '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 schneller mit KI 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', + ], + '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', + ], +]; diff --git a/lang/el/billing.php b/lang/el/billing.php index fa643fe71..9170c22f3 100644 --- a/lang/el/billing.php +++ b/lang/el/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Η πληρωμή ακυρώθηκε', 'cancelled_description' => 'Η πληρωμή σας ακυρώθηκε. Δεν έγινε καμία χρέωση.', 'retry' => 'Δοκιμάστε ξανά', + 'taking_long' => 'Παίρνει περισσότερο από το αναμενόμενο — περιμένετε, συνεχίζουμε τη ρύθμιση.', + 'live' => 'Ζωντανά', ], ]; diff --git a/lang/el/mcp.php b/lang/el/mcp.php new file mode 100644 index 000000000..4e0ace84f --- /dev/null +++ b/lang/el/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Συνδέστε βοηθούς AI για να δημιουργούν και να διαχειρίζονται αναρτήσεις με τον λογαριασμό TryPost σας.', + 'step_add' => 'Επικολλήστε το όνομα, το URL ή το config παρακάτω στην εφαρμογή σας. Η σύνδεση ανοίγει στο πρόγραμμα περιήγησης την πρώτη φορά.', + 'name_label' => 'Όνομα', + 'url_label' => 'URL διακομιστή', + 'config_label' => 'Config', + 'connected_title' => 'Συνδεδεμένες εφαρμογές', + 'connected_description' => 'Βοηθοί με σύνδεση από οποιονδήποτε σε αυτόν τον λογαριασμό. Μπορείτε να αποσυνδέσετε μόνο τους δικούς σας.', + 'connected_empty' => 'Τίποτα συνδεδεμένο ακόμα. Χρησιμοποιήστε Claude, ChatGPT ή άλλο client παραπάνω.', + 'connected_by' => 'Συνδέθηκε από :name', + 'disconnect' => 'Αποσύνδεση', + 'disconnect_title' => 'Αποσύνδεση εφαρμογής', + 'disconnect_confirm' => 'Αυτό αποσυνδέει την εφαρμογή από το TryPost. Θα χρειαστεί να συνδεθεί ξανά πριν χρησιμοποιήσει το MCP.', + 'disconnected' => 'Η εφαρμογή αποσυνδέθηκε.', + 'copied' => 'Αντιγράφηκε', + 'last_used' => 'Τελευταία χρήση', + 'never' => 'Ποτέ', + 'documentation_title' => 'Τεκμηρίωση', + 'documentation_description' => 'Οδηγοί ανά client, διαθέσιμα tools και αντιμετώπιση προβλημάτων.', + 'view_docs' => 'Δείτε την τεκμηρίωση', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Άλλες εφαρμογές', + 'other_clients_description' => 'Cursor, VS Code, Claude Code και ό,τι άλλο μιλάει MCP.', + + 'clients' => [ + 'cursor' => 'Προσθέστε το TryPost ως απομακρυσμένο MCP server στο Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Επικολλήστε το config παρακάτω στις ρυθμίσεις MCP του VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Επικολλήστε το config παρακάτω στις ρυθμίσεις MCP του Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Λειτουργεί με κάθε client που διαβάζει config mcpServers.', + 'other_name' => 'Άλλα', + ], +]; diff --git a/lang/el/onboarding.php b/lang/el/onboarding.php index d0eccc361..f26e4723e 100644 --- a/lang/el/onboarding.php +++ b/lang/el/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Καλώς ήρθες στο TryPost', + 'description' => 'Ακολούθησε τα παρακάτω βήματα για να δεις πώς λειτουργεί το TryPost και να δημοσιεύσεις την πρώτη σου ανάρτηση.', + 'skip' => 'Παράλειψη προς το παρόν', + 'continue' => 'Συνέχεια στο TryPost', + 'status' => [ + 'complete' => 'Ολοκληρώθηκε', + 'todo' => 'Εκκρεμεί', ], - 'goals_title' => 'Ποιος είναι ο στόχος σας με το TryPost;', - 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', - 'goals' => [ - 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', - 'ai_content' => 'Δημιουργία δημοσιεύσεων ταχύτερα με AI', - 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', - 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', - 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', - 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', - 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', - 'team_collaboration' => 'Συνεργασία με την ομάδα μου', - 'automate_api' => 'Αυτοματοποίηση δημοσιεύσεων με το API, το MCP ή κώδικα', - 'track_performance' => 'Παρακολούθηση της απόδοσης των δημοσιεύσεών μου', - 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', - 'other' => 'Κάτι άλλο', + 'mcp' => [ + 'title' => 'Σύνδεσε τον βοηθό AI σου', + 'description' => 'Πρόσθεσε το TryPost ως διακομιστή MCP ώστε ο βοηθός σου να δημιουργεί και να διαχειρίζεται social posts για εσένα.', + 'copy_step' => 'Αντίγραψε το URL του διακομιστή TryPost', + 'open_step' => 'Άνοιξε τον βοηθό AI σου', + 'copy' => 'Αντιγραφή URL', + 'copied' => 'Το URL MCP αντιγράφηκε.', + 'connect' => 'Σύνδεση με :client', + 'clients' => [ + 'claude' => 'Άνοιξε Settings → Connectors, πρόσθεσε έναν προσαρμοσμένο connector και επικόλλησε το παραπάνω URL.', + 'chatgpt' => 'Άνοιξε Settings → Apps & Connectors, δημιούργησε έναν προσαρμοσμένο connector και επικόλλησε το παραπάνω 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' => '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/settings.php b/lang/el/settings.php index 29dda286d..3ce5941f8 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Μάρκα', 'users' => 'Μέλη', 'api_keys' => 'Κλειδιά API', + 'mcp' => 'MCP', ], 'title' => 'Ρυθμίσεις workspace', 'logo_heading' => 'Λογότυπο workspace', diff --git a/lang/el/sidebar.php b/lang/el/sidebar.php index a19adb710..4f8738e79 100644 --- a/lang/el/sidebar.php +++ b/lang/el/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Δημιουργία workspace', 'create_post' => 'Δημιουργία δημοσίευσης', 'profile' => 'Προφίλ', + 'my_account' => 'Ο λογαριασμός μου', + 'account_settings' => 'Λογαριασμός και χρέωση', 'log_out' => 'Αποσύνδεση', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Στατιστικά', 'automations' => 'Αυτοματισμοί', 'settings' => 'Ρυθμίσεις', + 'onboarding' => 'Ξεκινώντας', + 'onboarding_hint' => 'Ολοκλήρωση ρύθμισης', 'posts' => [ 'calendar' => 'Ημερολόγιο', @@ -44,7 +48,9 @@ 'signatures' => 'Υπογραφές', 'labels' => 'Ετικέτες', 'assets' => 'Στοιχεία', + 'settings' => 'Ρυθμίσεις', 'api_keys' => 'Κλειδιά API', + 'mcp' => 'MCP', ], 'notifications' => 'Ειδοποιήσεις', diff --git a/lang/el/welcome.php b/lang/el/welcome.php new file mode 100644 index 000000000..5cdf305e4 --- /dev/null +++ b/lang/el/welcome.php @@ -0,0 +1,57 @@ + 'Τι σας περιγράφει καλύτερα;', + 'description' => 'Επιλέξτε την πιο κοντινή επιλογή και θα προσαρμόσουμε την εμπειρία σας.', + 'continue' => 'Συνέχεια', + 'checkout_owner_only' => 'Ζητήστε από τον κάτοχο του λογαριασμού να ολοκληρώσει την πληρωμή και να ξεκινήσει τη συνδρομή.', + 'checkout_trial_note' => 'Δωρεάν δοκιμή :days ημερών, μετά το :plan χρεώνεται μηνιαίως. Ακυρώστε όποτε θέλετε.', + 'checkout_plan_note' => 'Μετά την ολοκλήρωση, το :plan χρεώνεται μηνιαίως.', + 'subscription_required_title' => 'Αναμονή για τον κάτοχο του λογαριασμού', + 'subscription_required_description' => 'Αυτός ο λογαριασμός δεν έχει ακόμη ενεργή συνδρομή. Ζητήστε από τον κάτοχο να ολοκληρώσει την πληρωμή — θα έχετε πλήρη πρόσβαση μόλις ενεργοποιηθεί.', + 'subscription_required_owner' => 'Ο κάτοχος του λογαριασμού σας είναι ο/η :name.', + 'progress' => 'Πρόοδος καλωσορίσματος', + 'go_to_step' => 'Μετάβαση στο βήμα :step', + 'personas' => [ + 'creator' => 'Δημιουργός περιεχομένου', + 'freelancer' => 'Ελεύθερος επαγγελματίας', + 'developer' => 'Προγραμματιστής', + 'startup' => 'Startup', + 'agency' => 'Πρακτορείο', + 'small_business' => 'Μικρή επιχείρηση', + 'marketer' => 'Marketer', + 'online_store' => 'Ηλεκτρονικό κατάστημα', + 'other' => 'Άλλο', + ], + 'goals_title' => 'Ποιος είναι ο στόχος σας;', + 'goals_description' => 'Επιλέξτε ό,τι σας ταιριάζει και θα ρυθμίσουμε το TryPost για εσάς.', + 'goals' => [ + 'save_time' => 'Εξοικονόμηση χρόνου δημοσιεύοντας παντού ταυτόχρονα', + 'ai_content' => 'Δημιουργία δημοσιεύσεων ταχύτερα με AI', + 'plan_calendar' => 'Προγραμματισμός των δημοσιεύσεών μου σε ημερολόγιο', + 'stay_on_brand' => 'Διατήρηση κάθε δημοσίευσης εναρμονισμένης με τη μάρκα', + 'grow_audience' => 'Ανάπτυξη του κοινού και της αλληλεπίδρασής μου', + 'drive_sales' => 'Περισσότερη επισκεψιμότητα και πωλήσεις', + 'manage_clients' => 'Διαχείριση πολλών μαρκών ή πελατών', + 'just_exploring' => 'Απλώς εξερευνώ προς το παρόν', + 'other' => 'Κάτι άλλο', + ], + 'referral_source_title' => 'Πώς μας βρήκατε;', + 'referral_source_description' => 'Αυτό μας βοηθά να καταλάβουμε πώς οι άνθρωποι ανακαλύπτουν το TryPost.', + 'referral_source' => [ + 'google' => 'Google ή αναζήτηση', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram ή Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'Βοηθός AI (ChatGPT, Claude…)', + 'friend' => 'Φίλος ή συνάδελφος', + 'blog' => 'Blog, newsletter ή άρθρο', + 'other' => 'Κάτι άλλο', + ], +]; diff --git a/lang/en/billing.php b/lang/en/billing.php index ed4ffa1f7..22c672486 100644 --- a/lang/en/billing.php +++ b/lang/en/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Checkout cancelled', 'cancelled_description' => 'Your checkout was cancelled. No charges were made.', 'retry' => 'Try again', + 'taking_long' => 'This is taking longer than expected — hang tight, we are still setting things up.', + 'live' => 'Live', ], ]; diff --git a/lang/en/mcp.php b/lang/en/mcp.php new file mode 100644 index 000000000..c04037086 --- /dev/null +++ b/lang/en/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Connect AI assistants so they can create and manage posts with your TryPost account.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Connected apps', + 'connected_description' => 'Assistants signed in by anyone on this account. You can only disconnect your own apps.', + 'connected_empty' => 'Nothing connected yet. Use Claude, ChatGPT, or another client above.', + 'connected_by' => 'Connected by :name', + 'disconnect' => 'Disconnect', + 'disconnect_title' => 'Disconnect app', + 'disconnect_confirm' => 'This signs the app out of TryPost. It will need to reconnect before it can use MCP again.', + 'disconnected' => 'App disconnected.', + 'copied' => 'Copied', + 'last_used' => 'Last used', + 'never' => 'Never', + 'documentation_title' => 'Documentation', + 'documentation_description' => 'Client setup guides, available tools, and troubleshooting.', + 'view_docs' => 'View docs', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Other apps', + 'other_clients_description' => 'Cursor, VS Code, Claude Code, and anything else that speaks MCP.', + + 'clients' => [ + 'cursor' => 'Add TryPost as a remote MCP server in Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Paste the config below into VS Code\'s MCP settings.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Paste the config below into Claude Code\'s MCP settings.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Works with any client that reads an mcpServers config.', + 'other_name' => 'Other', + ], +]; diff --git a/lang/en/onboarding.php b/lang/en/onboarding.php index e76cf6717..20ae06597 100644 --- a/lang/en/onboarding.php +++ b/lang/en/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Welcome to TryPost', + 'description' => 'Follow the steps below to see how TryPost works and publish your first post.', + 'skip' => 'Skip for now', + 'continue' => 'Continue to TryPost', + 'status' => [ + 'complete' => 'Complete', + 'todo' => 'To do', ], - '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' => 'Create posts faster with AI', - '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', - 'team_collaboration' => 'Work with my team', - 'automate_api' => 'Automate posting with the API, MCP or code', - 'track_performance' => 'See how my posts perform', - '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.', + 'copy_step' => 'Copy your TryPost server URL', + 'open_step' => 'Open your AI assistant', + 'copy' => 'Copy URL', + 'copied' => 'MCP URL copied.', + 'connect' => 'Connect with :client', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, add a custom connector, then paste the URL above.', + 'chatgpt' => 'Open Settings → Apps & Connectors, create a custom connector, then paste the URL above.', + ], ], - '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/settings.php b/lang/en/settings.php index 52556caae..b4b7dc806 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Brand', 'users' => 'Members', 'api_keys' => 'API Keys', + 'mcp' => 'MCP', ], 'title' => 'Workspace settings', 'logo_heading' => 'Workspace logo', diff --git a/lang/en/sidebar.php b/lang/en/sidebar.php index f9cddbd69..02841ba57 100644 --- a/lang/en/sidebar.php +++ b/lang/en/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Create workspace', 'create_post' => 'Create post', 'profile' => 'Profile', + 'my_account' => 'My account', + 'account_settings' => 'Account & billing', 'log_out' => 'Log out', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automations', 'settings' => 'Settings', + 'onboarding' => 'Getting started', + 'onboarding_hint' => 'Finish setup', 'posts' => [ 'calendar' => 'Calendar', @@ -44,7 +48,9 @@ 'signatures' => 'Signatures', 'labels' => 'Labels', 'assets' => 'Assets', + 'settings' => 'Settings', 'api_keys' => 'API Keys', + 'mcp' => 'MCP', ], 'notifications' => 'Notifications', diff --git a/lang/en/welcome.php b/lang/en/welcome.php new file mode 100644 index 000000000..eea099393 --- /dev/null +++ b/lang/en/welcome.php @@ -0,0 +1,57 @@ + 'What best describes you?', + 'description' => 'Choose the closest match and we\'ll tailor your experience.', + 'continue' => 'Continue', + 'checkout_owner_only' => 'Ask the account owner to finish checkout and start your subscription.', + 'checkout_trial_note' => ':days-day free trial, then :plan bills monthly. Cancel anytime.', + 'checkout_plan_note' => 'After checkout, :plan bills monthly.', + 'subscription_required_title' => 'Waiting for the account owner', + 'subscription_required_description' => 'This account doesn\'t have an active subscription yet. Ask the account owner to finish checkout — you\'ll get full access as soon as it is active.', + 'subscription_required_owner' => 'Your account owner is :name.', + 'progress' => 'Welcome progress', + 'go_to_step' => 'Go to step :step', + 'personas' => [ + 'creator' => 'Content creator', + 'freelancer' => 'Freelancer', + 'developer' => 'Developer', + 'startup' => 'Startup', + 'agency' => 'Agency', + 'small_business' => 'Small business', + 'marketer' => 'Marketer', + 'online_store' => 'Online store', + 'other' => 'Other', + ], + 'goals_title' => 'What\'s your goal?', + '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' => 'Create posts faster with AI', + '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', + ], + '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', + ], +]; diff --git a/lang/es/billing.php b/lang/es/billing.php index deb54eb12..ed3d94b30 100644 --- a/lang/es/billing.php +++ b/lang/es/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Pago cancelado', 'cancelled_description' => 'Tu pago fue cancelado. No se realizaron cargos.', 'retry' => 'Intentar de nuevo', + 'taking_long' => 'Esto está tardando más de lo previsto: espera, seguimos configurando tu cuenta.', + 'live' => 'En vivo', ], ]; diff --git a/lang/es/mcp.php b/lang/es/mcp.php new file mode 100644 index 000000000..598bcad80 --- /dev/null +++ b/lang/es/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Conecta asistentes de IA para que creen y gestionen posts con tu cuenta de TryPost.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Apps conectadas', + 'connected_description' => 'Asistentes con sesión de cualquiera en esta cuenta. Solo puedes desconectar los tuyos.', + 'connected_empty' => 'Nada conectado aún. Usa Claude, ChatGPT u otro cliente arriba.', + 'connected_by' => 'Conectado por :name', + 'disconnect' => 'Desconectar', + 'disconnect_title' => 'Desconectar app', + 'disconnect_confirm' => 'Esto cierra la sesión de la app en TryPost. Tendrá que reconectar para usar MCP otra vez.', + 'disconnected' => 'App desconectada.', + 'copied' => 'Copiado', + 'last_used' => 'Último uso', + 'never' => 'Nunca', + 'documentation_title' => 'Documentación', + 'documentation_description' => 'Guías por cliente, tools disponibles y solución de problemas.', + 'view_docs' => 'Ver documentación', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Otras apps', + 'other_clients_description' => 'Cursor, VS Code, Claude Code y cualquier app que hable MCP.', + + 'clients' => [ + 'cursor' => 'Añade TryPost como servidor MCP remoto en Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Pega la configuración de abajo en los ajustes MCP de VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Pega la configuración de abajo en los ajustes MCP de Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Funciona con cualquier cliente que lea una config mcpServers.', + 'other_name' => 'Otros', + ], +]; diff --git a/lang/es/onboarding.php b/lang/es/onboarding.php index 4fb16d047..ac75e17df 100644 --- a/lang/es/onboarding.php +++ b/lang/es/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Bienvenido a TryPost', + 'description' => 'Sigue los pasos a continuación para ver cómo funciona TryPost y publicar tu primer post.', + 'skip' => 'Omitir por ahora', + 'continue' => 'Continuar a TryPost', + 'status' => [ + 'complete' => 'Completado', + 'todo' => 'Pendiente', ], - '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' => 'Crear publicaciones más rápido con IA', - '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', - 'team_collaboration' => 'Trabajar con mi equipo', - 'automate_api' => 'Automatizar publicaciones con la API, MCP o código', - 'track_performance' => 'Ver cómo rinden mis publicaciones', - '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.', + 'copy_step' => 'Copia la URL del servidor TryPost', + 'open_step' => 'Abre tu asistente de IA', + 'copy' => 'Copiar URL', + 'copied' => 'URL de MCP copiada.', + 'connect' => 'Conectar con :client', + 'clients' => [ + 'claude' => 'Abre Settings → Connectors, añade un conector personalizado y pega la URL de arriba.', + 'chatgpt' => 'Abre Settings → Apps & Connectors, crea un conector personalizado y pega la URL de arriba.', + ], ], - '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/settings.php b/lang/es/settings.php index 466776ab1..44310f2b3 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Marca', 'users' => 'Miembros', 'api_keys' => 'API Keys', + 'mcp' => 'MCP', ], 'title' => 'Configuración del workspace', 'logo_heading' => 'Logo del workspace', diff --git a/lang/es/sidebar.php b/lang/es/sidebar.php index c6f768d49..46accb9c6 100644 --- a/lang/es/sidebar.php +++ b/lang/es/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Crear workspace', 'create_post' => 'Crear post', 'profile' => 'Perfil', + 'my_account' => 'Mi cuenta', + 'account_settings' => 'Cuenta y facturación', 'log_out' => 'Cerrar sesión', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automatizaciones', 'settings' => 'Configuración', + 'onboarding' => 'Primeros pasos', + 'onboarding_hint' => 'Termina la configuración', 'posts' => [ 'calendar' => 'Calendario', @@ -44,7 +48,9 @@ 'signatures' => 'Firmas', 'labels' => 'Etiquetas', 'assets' => 'Medios', + 'settings' => 'Configuración', 'api_keys' => 'API Keys', + 'mcp' => 'MCP', ], 'notifications' => 'Notificaciones', diff --git a/lang/es/welcome.php b/lang/es/welcome.php new file mode 100644 index 000000000..759718205 --- /dev/null +++ b/lang/es/welcome.php @@ -0,0 +1,57 @@ + '¿Qué te describe mejor?', + 'description' => 'Elige la opción más cercana y personalizaremos tu experiencia.', + 'continue' => 'Continuar', + 'checkout_owner_only' => 'Pide al propietario de la cuenta que complete el checkout e inicie la suscripción.', + 'checkout_trial_note' => 'Prueba gratis de :days días; después, :plan se factura mensualmente. Cancela cuando quieras.', + 'checkout_plan_note' => 'Después del checkout, :plan se factura mensualmente.', + 'subscription_required_title' => 'Esperando al propietario de la cuenta', + 'subscription_required_description' => 'Esta cuenta aún no tiene una suscripción activa. Pide al propietario que complete el checkout: tendrás acceso total en cuanto esté activa.', + 'subscription_required_owner' => 'El propietario de tu cuenta es :name.', + 'progress' => 'Progreso de bienvenida', + 'go_to_step' => 'Ir al paso :step', + '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', + ], + 'goals_title' => '¿Cuál es tu objetivo?', + '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' => 'Crear publicaciones más rápido con IA', + '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', + ], + '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', + ], +]; diff --git a/lang/fr/billing.php b/lang/fr/billing.php index d423e220c..108d34586 100644 --- a/lang/fr/billing.php +++ b/lang/fr/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Paiement annulé', 'cancelled_description' => 'Votre paiement a été annulé. Aucun montant n\'a été débité.', 'retry' => 'Réessayer', + 'taking_long' => 'Cela prend plus de temps que prévu — patientez, la configuration est toujours en cours.', + 'live' => 'En direct', ], ]; diff --git a/lang/fr/mcp.php b/lang/fr/mcp.php new file mode 100644 index 000000000..21b10dfe0 --- /dev/null +++ b/lang/fr/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Connectez des assistants IA pour créer et gérer des posts avec votre compte TryPost.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Apps connectées', + 'connected_description' => 'Assistants connectés par n’importe qui sur ce compte. Vous ne pouvez déconnecter que les vôtres.', + 'connected_empty' => 'Rien de connecté pour l’instant. Utilisez Claude, ChatGPT ou un autre client ci-dessus.', + 'connected_by' => 'Connecté par :name', + 'disconnect' => 'Déconnecter', + 'disconnect_title' => 'Déconnecter l’app', + 'disconnect_confirm' => 'Cela déconnecte l’app de TryPost. Elle devra se reconnecter pour utiliser MCP à nouveau.', + 'disconnected' => 'App déconnectée.', + 'copied' => 'Copié', + 'last_used' => 'Dernière utilisation', + 'never' => 'Jamais', + 'documentation_title' => 'Documentation', + 'documentation_description' => 'Guides par client, tools disponibles et dépannage.', + 'view_docs' => 'Voir la documentation', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Autres apps', + 'other_clients_description' => 'Cursor, VS Code, Claude Code et toute app qui parle MCP.', + + 'clients' => [ + 'cursor' => 'Ajoutez TryPost comme serveur MCP distant dans Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Collez la configuration ci-dessous dans les paramètres MCP de VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Collez la configuration ci-dessous dans les paramètres MCP de Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Fonctionne avec tout client qui lit une config mcpServers.', + 'other_name' => 'Autres', + ], +]; diff --git a/lang/fr/onboarding.php b/lang/fr/onboarding.php index 7d103c102..cee058e29 100644 --- a/lang/fr/onboarding.php +++ b/lang/fr/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Bienvenue sur TryPost', + 'description' => 'Suivez les étapes ci-dessous pour découvrir comment TryPost fonctionne et publier votre premier post.', + 'skip' => 'Passer pour l’instant', + 'continue' => 'Continuer vers TryPost', + 'status' => [ + 'complete' => 'Terminé', + 'todo' => 'À faire', ], - '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' => 'Créer des publications plus vite avec l\'IA', - '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', - 'team_collaboration' => 'Travailler avec mon équipe', - 'automate_api' => 'Automatiser la publication avec l\'API, le MCP ou du code', - 'track_performance' => 'Voir les performances de mes publications', - '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.', + 'copy_step' => 'Copiez l’URL du serveur TryPost', + 'open_step' => 'Ouvrez votre assistant IA', + 'copy' => 'Copier l’URL', + 'copied' => 'URL MCP copiée.', + 'connect' => 'Connecter avec :client', + 'clients' => [ + 'claude' => 'Ouvrez Settings → Connectors, ajoutez un connecteur personnalisé, puis collez l’URL ci-dessus.', + 'chatgpt' => 'Ouvrez Settings → Apps & Connectors, créez un connecteur personnalisé, puis collez l’URL ci-dessus.', + ], ], - '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/settings.php b/lang/fr/settings.php index 8bfad5800..2cede7c15 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Marque', 'users' => 'Membres', 'api_keys' => 'Clés API', + 'mcp' => 'MCP', ], 'title' => 'Paramètres de l\'espace de travail', 'logo_heading' => 'Logo de l\'espace de travail', diff --git a/lang/fr/sidebar.php b/lang/fr/sidebar.php index a63250252..845f6ebca 100644 --- a/lang/fr/sidebar.php +++ b/lang/fr/sidebar.php @@ -8,6 +8,8 @@ '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', 'log_out' => 'Se déconnecter', 'workspace' => 'Espace de travail : :name', @@ -30,6 +32,8 @@ 'analytics' => 'Statistiques', 'automations' => 'Automatisations', 'settings' => 'Paramètres', + 'onboarding' => 'Premiers pas', + 'onboarding_hint' => 'Terminer la configuration', 'posts' => [ 'calendar' => 'Calendrier', @@ -44,7 +48,9 @@ 'signatures' => 'Signatures', 'labels' => 'Étiquettes', 'assets' => 'Médias', + 'settings' => 'Paramètres', 'api_keys' => 'Clés API', + 'mcp' => 'MCP', ], 'notifications' => 'Notifications', diff --git a/lang/fr/welcome.php b/lang/fr/welcome.php new file mode 100644 index 000000000..133588eb8 --- /dev/null +++ b/lang/fr/welcome.php @@ -0,0 +1,57 @@ + 'Qu\'est-ce qui vous décrit le mieux ?', + 'description' => 'Choisissez l\'option la plus proche et nous personnaliserons votre expérience.', + 'continue' => 'Continuer', + 'checkout_owner_only' => 'Demandez au propriétaire du compte de finaliser le paiement et de démarrer l\'abonnement.', + 'checkout_trial_note' => 'Essai gratuit de :days jours, puis :plan est facturé mensuellement. Annulez à tout moment.', + 'checkout_plan_note' => 'Après le paiement, :plan est facturé mensuellement.', + 'subscription_required_title' => 'En attente du propriétaire du compte', + 'subscription_required_description' => 'Ce compte n\'a pas encore d\'abonnement actif. Demandez au propriétaire de finaliser le paiement — vous aurez un accès complet dès qu\'il sera actif.', + 'subscription_required_owner' => 'Le propriétaire de votre compte est :name.', + 'progress' => 'Progression d’accueil', + 'go_to_step' => 'Aller à l’étape :step', + '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', + ], + 'goals_title' => 'Quel est votre objectif ?', + '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' => 'Créer des publications plus vite avec l\'IA', + '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', + ], + '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', + ], +]; diff --git a/lang/it/billing.php b/lang/it/billing.php index 2af61f74e..4407b879c 100644 --- a/lang/it/billing.php +++ b/lang/it/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Pagamento annullato', 'cancelled_description' => 'Il tuo pagamento è stato annullato. Non è stato effettuato alcun addebito.', 'retry' => 'Riprova', + 'taking_long' => 'Sta richiedendo più tempo del previsto: attendi, stiamo ancora configurando tutto.', + 'live' => 'Dal vivo', ], ]; diff --git a/lang/it/mcp.php b/lang/it/mcp.php new file mode 100644 index 000000000..75bb16a28 --- /dev/null +++ b/lang/it/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Collega assistenti IA così possono creare e gestire post con il tuo account TryPost.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'App collegate', + 'connected_description' => 'Assistenti con accesso di chiunque su questo account. Puoi disconnettere solo i tuoi.', + 'connected_empty' => 'Nessuna connessione ancora. Usa Claude, ChatGPT o un altro client sopra.', + 'connected_by' => 'Connesso da :name', + 'disconnect' => 'Scollega', + 'disconnect_title' => 'Scollega app', + 'disconnect_confirm' => 'Questo scollega l’app da TryPost. Dovrà riconnettersi prima di usare di nuovo MCP.', + 'disconnected' => 'App scollegata.', + 'copied' => 'Copiato', + 'last_used' => 'Ultimo uso', + 'never' => 'Mai', + 'documentation_title' => 'Documentazione', + 'documentation_description' => 'Guide per client, tools disponibili e risoluzione problemi.', + 'view_docs' => 'Vedi documentazione', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Altre app', + 'other_clients_description' => 'Cursor, VS Code, Claude Code e qualsiasi app che parla MCP.', + + 'clients' => [ + 'cursor' => 'Aggiungi TryPost come server MCP remoto in Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Incolla la config qui sotto nelle impostazioni MCP di VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Incolla la config qui sotto nelle impostazioni MCP di Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Funziona con qualsiasi client che legge una config mcpServers.', + 'other_name' => 'Altri', + ], +]; diff --git a/lang/it/onboarding.php b/lang/it/onboarding.php index 0beba0331..81135bee7 100644 --- a/lang/it/onboarding.php +++ b/lang/it/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Benvenuto su TryPost', + 'description' => 'Segui i passaggi qui sotto per vedere come funziona TryPost e pubblicare il tuo primo post.', + 'skip' => 'Salta per ora', + 'continue' => 'Continua su TryPost', + 'status' => [ + 'complete' => 'Completato', + 'todo' => 'Da fare', ], - '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' => 'Creare post più velocemente con l\'IA', - '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', - 'team_collaboration' => 'Lavorare con il mio team', - 'automate_api' => 'Automatizzare la pubblicazione con API, MCP o codice', - 'track_performance' => 'Vedere come vanno i miei post', - '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.', + 'copy_step' => 'Copia l’URL del server TryPost', + 'open_step' => 'Apri il tuo assistente IA', + 'copy' => 'Copia URL', + 'copied' => 'URL MCP copiato.', + 'connect' => 'Collega con :client', + 'clients' => [ + 'claude' => 'Apri Settings → Connectors, aggiungi un connettore personalizzato e incolla l’URL qui sopra.', + 'chatgpt' => 'Apri Settings → Apps & Connectors, crea un connettore personalizzato e incolla l’URL qui sopra.', + ], ], - '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/settings.php b/lang/it/settings.php index 3c3708c1f..08f8e3417 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Brand', 'users' => 'Membri', 'api_keys' => 'Chiavi API', + 'mcp' => 'MCP', ], 'title' => 'Impostazioni del workspace', 'logo_heading' => 'Logo del workspace', diff --git a/lang/it/sidebar.php b/lang/it/sidebar.php index fa517924a..5baa2ed16 100644 --- a/lang/it/sidebar.php +++ b/lang/it/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Crea workspace', 'create_post' => 'Crea post', 'profile' => 'Profilo', + 'my_account' => 'Il mio account', + 'account_settings' => 'Account e fatturazione', 'log_out' => 'Esci', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Statistiche', 'automations' => 'Automazioni', 'settings' => 'Impostazioni', + 'onboarding' => 'Primi passi', + 'onboarding_hint' => 'Completa la configurazione', 'posts' => [ 'calendar' => 'Calendario', @@ -44,7 +48,9 @@ 'signatures' => 'Firme', 'labels' => 'Etichette', 'assets' => 'Risorse', + 'settings' => 'Impostazioni', 'api_keys' => 'Chiavi API', + 'mcp' => 'MCP', ], 'notifications' => 'Notifiche', diff --git a/lang/it/welcome.php b/lang/it/welcome.php new file mode 100644 index 000000000..cf9b0082a --- /dev/null +++ b/lang/it/welcome.php @@ -0,0 +1,57 @@ + 'Cosa ti descrive meglio?', + 'description' => 'Scegli l\'opzione più vicina e personalizzeremo la tua esperienza.', + 'continue' => 'Continua', + 'checkout_owner_only' => 'Chiedi al proprietario dell\'account di completare il checkout e avviare l\'abbonamento.', + 'checkout_trial_note' => 'Prova gratuita di :days giorni, poi :plan viene addebitato mensilmente. Annulla quando vuoi.', + 'checkout_plan_note' => 'Dopo il checkout, :plan viene addebitato mensilmente.', + 'subscription_required_title' => 'In attesa del proprietario dell\'account', + 'subscription_required_description' => 'Questo account non ha ancora un abbonamento attivo. Chiedi al proprietario di completare il checkout: avrai accesso completo non appena sarà attivo.', + 'subscription_required_owner' => 'Il proprietario del tuo account è :name.', + 'progress' => 'Progresso di benvenuto', + 'go_to_step' => 'Vai al passaggio :step', + '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', + ], + 'goals_title' => 'Qual è il tuo obiettivo?', + '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' => 'Creare post più velocemente con l\'IA', + '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', + ], + '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', + ], +]; diff --git a/lang/ja/billing.php b/lang/ja/billing.php index 3b73b4ce2..40161027a 100644 --- a/lang/ja/billing.php +++ b/lang/ja/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'チェックアウトがキャンセルされました', 'cancelled_description' => 'チェックアウトはキャンセルされました。料金は請求されていません。', 'retry' => 'もう一度試す', + 'taking_long' => '通常より時間がかかっています。そのままお待ちください — 設定を続けています。', + 'live' => 'ライブ', ], ]; diff --git a/lang/ja/mcp.php b/lang/ja/mcp.php new file mode 100644 index 000000000..42a906b32 --- /dev/null +++ b/lang/ja/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'TryPostアカウントで投稿の作成・管理ができるよう、AIアシスタントを接続します。', + 'step_add' => '下の名前・URL・設定をアプリに貼り付けてください。初回接続時はブラウザでログインが開きます。', + 'name_label' => '名前', + 'url_label' => 'サーバーURL', + 'config_label' => '設定', + 'connected_title' => '接続済みアプリ', + 'connected_description' => 'このアカウントの誰かがサインインしたアシスタント。切断できるのは自分の接続だけです。', + 'connected_empty' => 'まだ接続がありません。上の Claude、ChatGPT、または他のクライアントを使ってください。', + 'connected_by' => ':name が接続', + 'disconnect' => '切断', + 'disconnect_title' => 'アプリを切断', + 'disconnect_confirm' => 'TryPostからアプリを切断します。再度MCPを使うには再接続が必要です。', + 'disconnected' => 'アプリを切断しました。', + 'copied' => 'コピーしました', + 'last_used' => '最終使用', + 'never' => 'なし', + 'documentation_title' => 'ドキュメント', + 'documentation_description' => 'クライアント別のセットアップ、利用可能なツール、トラブルシューティング。', + 'view_docs' => 'ドキュメントを見る', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'その他のアプリ', + 'other_clients_description' => 'Cursor、VS Code、Claude Code、その他MCP対応アプリ。', + + 'clients' => [ + 'cursor' => 'CursorでTryPostをリモートMCPサーバーとして追加します。', + 'cursor_name' => 'Cursor', + 'vscode' => '下の設定をVS CodeのMCP設定に貼り付けます。', + 'vscode_name' => 'VS Code', + 'claude_code' => '下の設定をClaude CodeのMCP設定に貼り付けます。', + 'claude_code_name' => 'Claude Code', + 'other' => 'mcpServers設定を読むクライアントならどれでも使えます。', + 'other_name' => 'その他', + ], +]; diff --git a/lang/ja/onboarding.php b/lang/ja/onboarding.php index db92adcf2..1de432627 100644 --- a/lang/ja/onboarding.php +++ b/lang/ja/onboarding.php @@ -3,55 +3,46 @@ 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さん', + 'welcome_anonymous' => 'TryPostへようこそ', + 'description' => '以下のステップでTryPostの使い方を確認し、最初の投稿を公開しましょう。', + 'skip' => '今はスキップ', + 'continue' => 'TryPostへ進む', + 'status' => [ + 'complete' => '完了', + 'todo' => '未完了', ], - 'goals_title' => 'TryPost での目標は何ですか?', - 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', - 'goals' => [ - 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', - 'ai_content' => 'AI でより速く投稿を作成する', - 'plan_calendar' => 'カレンダーで投稿を計画する', - 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', - 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', - 'drive_sales' => 'トラフィックと売上を増やす', - 'manage_clients' => '複数のブランドやクライアントを管理する', - 'team_collaboration' => 'チームで作業する', - 'automate_api' => 'API、MCP、コードで投稿を自動化する', - 'track_performance' => '投稿のパフォーマンスを確認する', - 'just_exploring' => '今はまだ様子を見ている', - 'other' => 'その他', + 'mcp' => [ + 'title' => 'AIアシスタントを接続', + 'description' => 'TryPostをMCPサーバーとして追加すると、アシスタントがSNS投稿の作成・管理を行えます。', + 'copy_step' => 'TryPostサーバーURLをコピー', + 'open_step' => 'AIアシスタントを開く', + 'copy' => 'URLをコピー', + 'copied' => 'MCP URLをコピーしました。', + 'connect' => ':clientで接続', + 'clients' => [ + 'claude' => 'Settings → Connectors を開き、カスタムコネクタを追加して上のURLを貼り付けます。', + 'chatgpt' => 'Settings → Apps & Connectors を開き、カスタムコネクタを作成して上の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/settings.php b/lang/ja/settings.php index cdf5aa5c8..94e76beb7 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -129,6 +129,7 @@ 'brand' => 'ブランド', 'users' => 'メンバー', 'api_keys' => 'API キー', + 'mcp' => 'MCP', ], 'title' => 'ワークスペース設定', 'logo_heading' => 'ワークスペースのロゴ', diff --git a/lang/ja/sidebar.php b/lang/ja/sidebar.php index 09ff7c59d..403c08162 100644 --- a/lang/ja/sidebar.php +++ b/lang/ja/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'ワークスペースを作成', 'create_post' => '投稿を作成', 'profile' => 'プロフィール', + 'my_account' => 'マイアカウント', + 'account_settings' => 'アカウントと請求', 'log_out' => 'ログアウト', 'workspace' => 'ワークスペース: :name', @@ -30,6 +32,8 @@ 'analytics' => 'アナリティクス', 'automations' => 'オートメーション', 'settings' => '設定', + 'onboarding' => 'はじめに', + 'onboarding_hint' => 'セットアップを完了', 'posts' => [ 'calendar' => 'カレンダー', @@ -44,7 +48,9 @@ 'signatures' => '署名', 'labels' => 'ラベル', 'assets' => 'アセット', + 'settings' => '設定', 'api_keys' => 'API キー', + 'mcp' => 'MCP', ], 'notifications' => '通知', diff --git a/lang/ja/welcome.php b/lang/ja/welcome.php new file mode 100644 index 000000000..06a98a4ef --- /dev/null +++ b/lang/ja/welcome.php @@ -0,0 +1,57 @@ + 'あなたに一番近いのは?', + 'description' => '近いものを選ぶと、体験を最適化します。', + 'continue' => '続ける', + 'checkout_owner_only' => 'アカウントのオーナーにチェックアウトを完了してサブスクリプションを開始するよう依頼してください。', + 'checkout_trial_note' => ':days日間の無料トライアル後、:planが月額課金されます。いつでもキャンセルできます。', + 'checkout_plan_note' => 'チェックアウト後、:planが月額課金されます。', + 'subscription_required_title' => 'アカウントのオーナーを待っています', + 'subscription_required_description' => 'このアカウントにはまだ有効なサブスクリプションがありません。オーナーにチェックアウトの完了を依頼してください — 有効になり次第、フルアクセスできます。', + 'subscription_required_owner' => 'アカウントのオーナーは :name です。', + 'progress' => 'ようこそ進捗', + 'go_to_step' => 'ステップ :step へ', + 'personas' => [ + 'creator' => 'コンテンツクリエイター', + 'freelancer' => 'フリーランス', + 'developer' => '開発者', + 'startup' => 'スタートアップ', + 'agency' => '代理店', + 'small_business' => '中小企業', + 'marketer' => 'マーケター', + 'online_store' => 'オンラインストア', + 'other' => 'その他', + ], + 'goals_title' => '目標は何ですか?', + 'goals_description' => '当てはまるものをすべて選んでください。TryPost をあなた向けに設定します。', + 'goals' => [ + 'save_time' => 'すべての場所へ一度に投稿して時間を節約する', + 'ai_content' => 'AI でより速く投稿を作成する', + 'plan_calendar' => 'カレンダーで投稿を計画する', + 'stay_on_brand' => 'すべての投稿をブランドに沿ったものにする', + 'grow_audience' => 'オーディエンスとエンゲージメントを増やす', + 'drive_sales' => 'トラフィックと売上を増やす', + 'manage_clients' => '複数のブランドやクライアントを管理する', + 'just_exploring' => '今はまだ様子を見ている', + 'other' => 'その他', + ], + 'referral_source_title' => 'どこで私たちを知りましたか?', + 'referral_source_description' => 'これは、人々がどのように TryPost を見つけるかを理解するのに役立ちます。', + 'referral_source' => [ + 'google' => 'Google または検索', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram または Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI アシスタント(ChatGPT、Claude など)', + 'friend' => '友人または同僚', + 'blog' => 'ブログ、ニュースレター、記事', + 'other' => 'その他', + ], +]; diff --git a/lang/ko/billing.php b/lang/ko/billing.php index 43b33b21f..f4a6e5cb8 100644 --- a/lang/ko/billing.php +++ b/lang/ko/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => '결제 취소됨', 'cancelled_description' => '결제가 취소되었습니다. 요금이 청구되지 않았습니다.', 'retry' => '다시 시도', + 'taking_long' => '예상보다 오래 걸리고 있습니다. 잠시만 기다려 주세요 — 계속 설정 중입니다.', + 'live' => '라이브', ], ]; diff --git a/lang/ko/mcp.php b/lang/ko/mcp.php new file mode 100644 index 000000000..1048d656b --- /dev/null +++ b/lang/ko/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'TryPost 계정으로 게시물을 만들고 관리할 수 있도록 AI 어시스턴트를 연결하세요.', + 'step_add' => '아래 이름, URL 또는 설정을 앱에 붙여넣으세요. 처음 연결할 때 브라우저에서 로그인이 열립니다.', + 'name_label' => '이름', + 'url_label' => '서버 URL', + 'config_label' => '설정', + 'connected_title' => '연결된 앱', + 'connected_description' => '이 계정의 누구나 로그인한 어시스턴트입니다. 본인 연결만 해제할 수 있습니다.', + 'connected_empty' => '아직 연결된 앱이 없습니다. 위의 Claude, ChatGPT 또는 다른 클라이언트를 사용하세요.', + 'connected_by' => ':name님이 연결함', + 'disconnect' => '연결 해제', + 'disconnect_title' => '앱 연결 해제', + 'disconnect_confirm' => 'TryPost에서 앱 로그인을 해제합니다. MCP를 다시 쓰려면 다시 연결해야 합니다.', + 'disconnected' => '앱 연결이 해제되었습니다.', + 'copied' => '복사됨', + 'last_used' => '최근 사용', + 'never' => '없음', + 'documentation_title' => '문서', + 'documentation_description' => '클라이언트별 설정 가이드, 사용 가능한 도구, 문제 해결.', + 'view_docs' => '문서 보기', + 'connector_name' => 'TryPost', + + 'other_clients_title' => '다른 앱', + 'other_clients_description' => 'Cursor, VS Code, Claude Code 및 MCP를 지원하는 모든 앱.', + + 'clients' => [ + 'cursor' => 'Cursor에서 TryPost를 원격 MCP 서버로 추가하세요.', + 'cursor_name' => 'Cursor', + 'vscode' => '아래 설정을 VS Code MCP 설정에 붙여넣으세요.', + 'vscode_name' => 'VS Code', + 'claude_code' => '아래 설정을 Claude Code MCP 설정에 붙여넣으세요.', + 'claude_code_name' => 'Claude Code', + 'other' => 'mcpServers 설정을 읽는 모든 클라이언트에서 동작합니다.', + 'other_name' => '기타', + ], +]; diff --git a/lang/ko/onboarding.php b/lang/ko/onboarding.php index fc954799f..1ba62cb83 100644 --- a/lang/ko/onboarding.php +++ b/lang/ko/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'TryPost에 오신 걸 환영해요', + 'description' => '아래 단계를 따라 TryPost 사용법을 확인하고 첫 게시물을 발행하세요.', + 'skip' => '지금은 건너뛰기', + 'continue' => 'TryPost로 계속', + 'status' => [ + 'complete' => '완료', + 'todo' => '할 일', ], - 'goals_title' => 'TryPost로 이루려는 목표는 무엇인가요?', - 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', - 'goals' => [ - 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', - 'ai_content' => 'AI로 더 빠르게 게시물 작성', - 'plan_calendar' => '캘린더에서 게시물 계획', - 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', - 'grow_audience' => '팔로워와 참여 늘리기', - 'drive_sales' => '더 많은 트래픽과 판매 유도', - 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', - 'team_collaboration' => '팀과 협업', - 'automate_api' => 'API, MCP 또는 코드로 게시 자동화', - 'track_performance' => '게시물 성과 확인', - 'just_exploring' => '지금은 둘러보는 중', - 'other' => '다른 것', + 'mcp' => [ + 'title' => 'AI 어시스턴트 연결', + 'description' => 'TryPost를 MCP 서버로 추가하면 어시스턴트가 소셜 게시물을 만들고 관리할 수 있어요.', + 'copy_step' => 'TryPost 서버 URL 복사', + 'open_step' => 'AI 어시스턴트 열기', + 'copy' => 'URL 복사', + 'copied' => 'MCP URL이 복사되었어요.', + 'connect' => ':client로 연결', + 'clients' => [ + 'claude' => 'Settings → Connectors를 열고 커스텀 커넥터를 추가한 뒤 위 URL을 붙여넣으세요.', + 'chatgpt' => 'Settings → Apps & Connectors를 열고 커스텀 커넥터를 만든 뒤 위 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/settings.php b/lang/ko/settings.php index d20f1cdf9..77fb6569b 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -129,6 +129,7 @@ 'brand' => '브랜드', 'users' => '멤버', 'api_keys' => 'API 키', + 'mcp' => 'MCP', ], 'title' => '워크스페이스 설정', 'logo_heading' => '워크스페이스 로고', diff --git a/lang/ko/sidebar.php b/lang/ko/sidebar.php index 3d47a2bfe..9563b592d 100644 --- a/lang/ko/sidebar.php +++ b/lang/ko/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => '워크스페이스 만들기', 'create_post' => '게시물 만들기', 'profile' => '프로필', + 'my_account' => '내 계정', + 'account_settings' => '계정 및 결제', 'log_out' => '로그아웃', 'workspace' => '워크스페이스: :name', @@ -30,6 +32,8 @@ 'analytics' => '분석', 'automations' => '자동화', 'settings' => '설정', + 'onboarding' => '시작하기', + 'onboarding_hint' => '설정 마치기', 'posts' => [ 'calendar' => '캘린더', @@ -44,7 +48,9 @@ 'signatures' => '서명', 'labels' => '라벨', 'assets' => '에셋', + 'settings' => '설정', 'api_keys' => 'API 키', + 'mcp' => 'MCP', ], 'notifications' => '알림', diff --git a/lang/ko/welcome.php b/lang/ko/welcome.php new file mode 100644 index 000000000..3b0ae9cdd --- /dev/null +++ b/lang/ko/welcome.php @@ -0,0 +1,57 @@ + '무엇을 가장 잘 설명하나요?', + 'description' => '가장 가까운 항목을 선택하면 맞춤 경험을 제공해 드립니다.', + 'continue' => '계속', + 'checkout_owner_only' => '계정 소유자에게 결제와 구독 시작을 요청하세요.', + 'checkout_trial_note' => ':days일 무료 체험 후 :plan 요금이 매월 청구됩니다. 언제든 취소하세요.', + 'checkout_plan_note' => '결제 후 :plan 요금이 매월 청구됩니다.', + 'subscription_required_title' => '계정 소유자를 기다리는 중', + 'subscription_required_description' => '이 계정에는 아직 활성 구독이 없습니다. 소유자에게 결제 완료를 요청하세요 — 활성화되는 즉시 모든 기능을 사용할 수 있습니다.', + 'subscription_required_owner' => '계정 소유자는 :name 님입니다.', + 'progress' => '환영 진행률', + 'go_to_step' => ':step단계로 이동', + 'personas' => [ + 'creator' => '콘텐츠 크리에이터', + 'freelancer' => '프리랜서', + 'developer' => '개발자', + 'startup' => '스타트업', + 'agency' => '에이전시', + 'small_business' => '소상공인', + 'marketer' => '마케터', + 'online_store' => '온라인 스토어', + 'other' => '기타', + ], + 'goals_title' => '목표가 무엇인가요?', + 'goals_description' => '해당되는 항목을 모두 선택하면 TryPost를 맞춤 설정해 드립니다.', + 'goals' => [ + 'save_time' => '한 번에 여러 곳에 게시하여 시간 절약', + 'ai_content' => 'AI로 더 빠르게 게시물 작성', + 'plan_calendar' => '캘린더에서 게시물 계획', + 'stay_on_brand' => '모든 게시물을 브랜드에 맞게 유지', + 'grow_audience' => '팔로워와 참여 늘리기', + 'drive_sales' => '더 많은 트래픽과 판매 유도', + 'manage_clients' => '여러 브랜드 또는 클라이언트 관리', + 'just_exploring' => '지금은 둘러보는 중', + 'other' => '다른 것', + ], + 'referral_source_title' => '저희를 어떻게 알게 되셨나요?', + 'referral_source_description' => '사람들이 TryPost를 어떻게 발견하는지 파악하는 데 도움이 됩니다.', + 'referral_source' => [ + 'google' => 'Google 또는 검색', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram 또는 Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI 어시스턴트 (ChatGPT, Claude 등)', + 'friend' => '친구 또는 동료', + 'blog' => '블로그, 뉴스레터 또는 기사', + 'other' => '기타', + ], +]; diff --git a/lang/nl/billing.php b/lang/nl/billing.php index 722e46c16..96b81d57c 100644 --- a/lang/nl/billing.php +++ b/lang/nl/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Afrekenen geannuleerd', 'cancelled_description' => 'Je afrekenen is geannuleerd. Er zijn geen kosten in rekening gebracht.', 'retry' => 'Opnieuw proberen', + 'taking_long' => 'Dit duurt langer dan verwacht — even geduld, we zijn nog bezig met de installatie.', + 'live' => 'Live', ], ]; diff --git a/lang/nl/mcp.php b/lang/nl/mcp.php new file mode 100644 index 000000000..f77435348 --- /dev/null +++ b/lang/nl/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Koppel AI-assistenten zodat ze posts kunnen maken en beheren met je TryPost-account.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Gekoppelde apps', + 'connected_description' => 'Assistenten waarmee iemand op dit account is ingelogd. Je kunt alleen je eigen apps ontkoppelen.', + 'connected_empty' => 'Nog niets gekoppeld. Gebruik Claude, ChatGPT of een andere client hierboven.', + 'connected_by' => 'Verbonden door :name', + 'disconnect' => 'Ontkoppelen', + 'disconnect_title' => 'App ontkoppelen', + 'disconnect_confirm' => 'Dit logt de app uit bij TryPost. Hij moet opnieuw verbinden om MCP weer te gebruiken.', + 'disconnected' => 'App ontkoppeld.', + 'copied' => 'Gekopieerd', + 'last_used' => 'Laatst gebruikt', + 'never' => 'Nooit', + 'documentation_title' => 'Documentatie', + 'documentation_description' => 'Handleidingen per client, beschikbare tools en probleemoplossing.', + 'view_docs' => 'Documentatie bekijken', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Andere apps', + 'other_clients_description' => 'Cursor, VS Code, Claude Code en alles wat MCP spreekt.', + + 'clients' => [ + 'cursor' => 'Voeg TryPost toe als remote MCP-server in Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Plak de config hieronder in de MCP-instellingen van VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Plak de config hieronder in de MCP-instellingen van Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Werkt met elke client die een mcpServers-config leest.', + 'other_name' => 'Overig', + ], +]; diff --git a/lang/nl/onboarding.php b/lang/nl/onboarding.php index 424cfe421..529e3cd3b 100644 --- a/lang/nl/onboarding.php +++ b/lang/nl/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Welkom bij TryPost', + 'description' => 'Volg de stappen hieronder om te zien hoe TryPost werkt en je eerste post te publiceren.', + 'skip' => 'Voor nu overslaan', + 'continue' => 'Doorgaan naar TryPost', + 'status' => [ + 'complete' => 'Voltooid', + 'todo' => 'Te doen', ], - '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' => 'Sneller posts maken met AI', - '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', - 'team_collaboration' => 'Samenwerken met mijn team', - 'automate_api' => 'Posten automatiseren met de API, MCP of code', - 'track_performance' => 'Zien hoe mijn posts presteren', - '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.', + 'copy_step' => 'Kopieer je TryPost-server-URL', + 'open_step' => 'Open je AI-assistent', + 'copy' => 'URL kopiëren', + 'copied' => 'MCP-URL gekopieerd.', + 'connect' => 'Verbinden met :client', + 'clients' => [ + 'claude' => 'Open Settings → Connectors, voeg een aangepaste connector toe en plak de URL hierboven.', + 'chatgpt' => 'Open Settings → Apps & Connectors, maak een aangepaste connector aan en plak de URL hierboven.', + ], ], - '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/settings.php b/lang/nl/settings.php index 78d7e05a1..46c1eb79d 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Merk', 'users' => 'Leden', 'api_keys' => 'API-sleutels', + 'mcp' => 'MCP', ], 'title' => 'Workspace-instellingen', 'logo_heading' => 'Workspace-logo', diff --git a/lang/nl/sidebar.php b/lang/nl/sidebar.php index daca60f21..9390f1ef8 100644 --- a/lang/nl/sidebar.php +++ b/lang/nl/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Workspace aanmaken', 'create_post' => 'Post aanmaken', 'profile' => 'Profiel', + 'my_account' => 'Mijn account', + 'account_settings' => 'Account en facturatie', 'log_out' => 'Uitloggen', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Statistieken', 'automations' => 'Automatiseringen', 'settings' => 'Instellingen', + 'onboarding' => 'Aan de slag', + 'onboarding_hint' => 'Setup afronden', 'posts' => [ 'calendar' => 'Kalender', @@ -44,7 +48,9 @@ 'signatures' => 'Handtekeningen', 'labels' => 'Labels', 'assets' => 'Assets', + 'settings' => 'Instellingen', 'api_keys' => 'API-sleutels', + 'mcp' => 'MCP', ], 'notifications' => 'Meldingen', diff --git a/lang/nl/welcome.php b/lang/nl/welcome.php new file mode 100644 index 000000000..67ce411ea --- /dev/null +++ b/lang/nl/welcome.php @@ -0,0 +1,57 @@ + 'Wat omschrijft jou het beste?', + 'description' => 'Kies de dichtstbijzijnde optie, dan stemmen we je ervaring af.', + 'continue' => 'Doorgaan', + 'checkout_owner_only' => 'Vraag de accounteigenaar om de checkout af te ronden en het abonnement te starten.', + 'checkout_trial_note' => ':days dagen gratis proberen, daarna wordt :plan maandelijks gefactureerd. Altijd opzegbaar.', + 'checkout_plan_note' => 'Na de checkout wordt :plan maandelijks gefactureerd.', + 'subscription_required_title' => 'Wachten op de accounteigenaar', + 'subscription_required_description' => 'Dit account heeft nog geen actief abonnement. Vraag de eigenaar om de checkout af te ronden — je krijgt volledige toegang zodra het actief is.', + 'subscription_required_owner' => 'De accounteigenaar is :name.', + 'progress' => 'Welkomstvoortgang', + 'go_to_step' => 'Ga naar stap :step', + 'personas' => [ + 'creator' => 'Contentmaker', + 'freelancer' => 'Freelancer', + 'developer' => 'Ontwikkelaar', + 'startup' => 'Startup', + 'agency' => 'Bureau', + 'small_business' => 'Klein bedrijf', + 'marketer' => 'Marketeer', + 'online_store' => 'Webshop', + 'other' => 'Anders', + ], + 'goals_title' => 'Wat is je doel?', + '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' => 'Sneller posts maken met AI', + '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', + ], + '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', + ], +]; diff --git a/lang/pl/billing.php b/lang/pl/billing.php index 4283acf93..4ce15dc16 100644 --- a/lang/pl/billing.php +++ b/lang/pl/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Anulowano płatność', 'cancelled_description' => 'Twoja płatność została anulowana. Nie pobrano żadnych opłat.', 'retry' => 'Spróbuj ponownie', + 'taking_long' => 'To trwa dłużej niż oczekiwano — poczekaj, wciąż wszystko konfigurujemy.', + 'live' => 'Na żywo', ], ]; diff --git a/lang/pl/mcp.php b/lang/pl/mcp.php new file mode 100644 index 000000000..3c24c5d05 --- /dev/null +++ b/lang/pl/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Połącz asystentów AI, aby tworzyli i zarządzali postami na koncie TryPost.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Połączone aplikacje', + 'connected_description' => 'Asystenci zalogowani przez kogokolwiek na tym koncie. Możesz rozłączyć tylko własne.', + 'connected_empty' => 'Nic jeszcze nie połączono. Użyj Claude, ChatGPT lub innego klienta powyżej.', + 'connected_by' => 'Połączono przez :name', + 'disconnect' => 'Rozłącz', + 'disconnect_title' => 'Rozłącz aplikację', + 'disconnect_confirm' => 'To wyloguje aplikację z TryPost. Musi połączyć się ponownie, zanim znów użyje MCP.', + 'disconnected' => 'Aplikacja rozłączona.', + 'copied' => 'Skopiowano', + 'last_used' => 'Ostatnie użycie', + 'never' => 'Nigdy', + 'documentation_title' => 'Dokumentacja', + 'documentation_description' => 'Przewodniki per klient, dostępne tools i rozwiązywanie problemów.', + 'view_docs' => 'Zobacz dokumentację', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Inne aplikacje', + 'other_clients_description' => 'Cursor, VS Code, Claude Code i wszystko, co mówi MCP.', + + 'clients' => [ + 'cursor' => 'Dodaj TryPost jako zdalny serwer MCP w Cursorze.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Wklej poniższą konfigurację w ustawieniach MCP VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Wklej poniższą konfigurację w ustawieniach MCP Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Działa z każdym klientem, który czyta config mcpServers.', + 'other_name' => 'Inne', + ], +]; diff --git a/lang/pl/onboarding.php b/lang/pl/onboarding.php index 91092398d..fb875d043 100644 --- a/lang/pl/onboarding.php +++ b/lang/pl/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Witaj w TryPost', + 'description' => 'Wykonaj poniższe kroki, aby zobaczyć, jak działa TryPost, i opublikować pierwszy post.', + 'skip' => 'Pomiń na razie', + 'continue' => 'Przejdź do TryPost', + 'status' => [ + 'complete' => 'Ukończone', + 'todo' => 'Do zrobienia', ], - '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' => 'Twórz posty szybciej dzięki AI', - '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', - 'team_collaboration' => 'Pracuj z moim zespołem', - 'automate_api' => 'Automatyzuj publikowanie za pomocą API, MCP lub kodu', - 'track_performance' => 'Sprawdzaj, jak radzą sobie moje posty', - '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.', + 'copy_step' => 'Skopiuj URL serwera TryPost', + 'open_step' => 'Otwórz asystenta AI', + 'copy' => 'Kopiuj URL', + 'copied' => 'URL MCP skopiowany.', + 'connect' => 'Połącz z :client', + 'clients' => [ + 'claude' => 'Otwórz Settings → Connectors, dodaj niestandardowy connector, a następnie wklej powyższy URL.', + 'chatgpt' => 'Otwórz Settings → Apps & Connectors, utwórz niestandardowy connector, a następnie wklej powyższy URL.', + ], ], - '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/settings.php b/lang/pl/settings.php index bde5ddc37..89520348f 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Marka', 'users' => 'Członkowie', 'api_keys' => 'Klucze API', + 'mcp' => 'MCP', ], 'title' => 'Ustawienia przestrzeni roboczej', 'logo_heading' => 'Logo przestrzeni roboczej', diff --git a/lang/pl/sidebar.php b/lang/pl/sidebar.php index 9e83547d1..0b9862b18 100644 --- a/lang/pl/sidebar.php +++ b/lang/pl/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Utwórz przestrzeń roboczą', 'create_post' => 'Utwórz post', 'profile' => 'Profil', + 'my_account' => 'Moje konto', + 'account_settings' => 'Konto i płatności', 'log_out' => 'Wyloguj się', 'workspace' => 'Przestrzeń robocza: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Analityka', 'automations' => 'Automatyzacje', 'settings' => 'Ustawienia', + 'onboarding' => 'Pierwsze kroki', + 'onboarding_hint' => 'Dokończ konfigurację', 'posts' => [ 'calendar' => 'Kalendarz', @@ -44,7 +48,9 @@ 'signatures' => 'Sygnatury', 'labels' => 'Etykiety', 'assets' => 'Zasoby', + 'settings' => 'Ustawienia', 'api_keys' => 'Klucze API', + 'mcp' => 'MCP', ], 'notifications' => 'Powiadomienia', diff --git a/lang/pl/welcome.php b/lang/pl/welcome.php new file mode 100644 index 000000000..3330fca05 --- /dev/null +++ b/lang/pl/welcome.php @@ -0,0 +1,57 @@ + 'Co najlepiej Cię opisuje?', + 'description' => 'Wybierz najbliższą opcję, a my dopasujemy Twoje doświadczenie.', + 'continue' => 'Kontynuuj', + 'checkout_owner_only' => 'Poproś właściciela konta o dokończenie płatności i rozpoczęcie subskrypcji.', + 'checkout_trial_note' => ':days dni za darmo, potem :plan rozliczany miesięcznie. Anuluj w każdej chwili.', + 'checkout_plan_note' => 'Po zakupie :plan jest rozliczany miesięcznie.', + 'subscription_required_title' => 'Oczekiwanie na właściciela konta', + 'subscription_required_description' => 'To konto nie ma jeszcze aktywnej subskrypcji. Poproś właściciela o dokończenie płatności — uzyskasz pełny dostęp, gdy tylko będzie aktywna.', + 'subscription_required_owner' => 'Właścicielem Twojego konta jest :name.', + 'progress' => 'Postęp powitalny', + 'go_to_step' => 'Przejdź do kroku :step', + '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', + ], + 'goals_title' => 'Jaki jest Twój cel?', + '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' => 'Twórz posty szybciej dzięki AI', + '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', + ], + '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', + ], +]; diff --git a/lang/pt-BR/billing.php b/lang/pt-BR/billing.php index 52e5e9f78..65a67daa1 100644 --- a/lang/pt-BR/billing.php +++ b/lang/pt-BR/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Pagamento cancelado', 'cancelled_description' => 'Seu pagamento foi cancelado. Nenhuma cobrança foi realizada.', 'retry' => 'Tentar novamente', + 'taking_long' => 'Isso está demorando mais que o normal — aguarde, ainda estamos configurando tudo.', + 'live' => 'Ao vivo', ], ]; diff --git a/lang/pt-BR/mcp.php b/lang/pt-BR/mcp.php new file mode 100644 index 000000000..8818a660c --- /dev/null +++ b/lang/pt-BR/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Conecte assistentes de IA pra criarem e gerenciarem posts com sua conta TryPost.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Apps conectados', + 'connected_description' => 'Assistentes com login de qualquer pessoa nesta conta. Você só desconecta os seus.', + 'connected_empty' => 'Nada conectado ainda. Use Claude, ChatGPT ou outro cliente acima.', + 'connected_by' => 'Conectado por :name', + 'disconnect' => 'Desconectar', + 'disconnect_title' => 'Desconectar app', + 'disconnect_confirm' => 'Isso desconecta o app do TryPost. Ele precisa reconectar pra usar o MCP de novo.', + 'disconnected' => 'App desconectado.', + 'copied' => 'Copiado', + 'last_used' => 'Último uso', + 'never' => 'Nunca', + 'documentation_title' => 'Documentação', + 'documentation_description' => 'Guias por cliente, tools disponíveis e solução de problemas.', + 'view_docs' => 'Ver documentação', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Outros apps', + 'other_clients_description' => 'Cursor, VS Code, Claude Code e qualquer app que fale MCP.', + + 'clients' => [ + 'cursor' => 'Adicione o TryPost como servidor MCP remoto no Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Cole o config abaixo nas configurações MCP do VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Cole o config abaixo nas configurações MCP do Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Funciona com qualquer cliente que leia um config mcpServers.', + 'other_name' => 'Outros', + ], +]; diff --git a/lang/pt-BR/onboarding.php b/lang/pt-BR/onboarding.php index 12ad79629..21c189d89 100644 --- a/lang/pt-BR/onboarding.php +++ b/lang/pt-BR/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Boas-vindas ao TryPost', + 'description' => 'Siga os passos abaixo pra ver como o TryPost funciona e publicar seu primeiro post.', + 'skip' => 'Pular por agora', + 'continue' => 'Continuar no TryPost', + 'status' => [ + 'complete' => 'Concluído', + 'todo' => 'Pendente', ], - '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' => 'Criar posts mais rápido com IA', - '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', - 'team_collaboration' => 'Trabalhar com meu time', - 'automate_api' => 'Automatizar publicações com a API, MCP ou código', - 'track_performance' => 'Ver o desempenho dos meus posts', - '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ê.', + 'copy_step' => 'Copie a URL do servidor TryPost', + 'open_step' => 'Abra seu assistente de IA', + 'copy' => 'Copiar URL', + 'copied' => 'URL do MCP copiada.', + 'connect' => 'Conectar com :client', + 'clients' => [ + 'claude' => 'Abra Settings → Connectors, adicione um connector customizado e cole a URL acima.', + 'chatgpt' => 'Abra Settings → Apps & Connectors, crie um connector customizado e cole a URL acima.', + ], ], - '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/settings.php b/lang/pt-BR/settings.php index 3ba148481..1ec2a7936 100644 --- a/lang/pt-BR/settings.php +++ b/lang/pt-BR/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Marca', 'users' => 'Membros', 'api_keys' => 'API Keys', + 'mcp' => 'MCP', ], 'title' => 'Configurações do workspace', 'logo_heading' => 'Logo do workspace', diff --git a/lang/pt-BR/sidebar.php b/lang/pt-BR/sidebar.php index 0717d6215..3b2a4a0f4 100644 --- a/lang/pt-BR/sidebar.php +++ b/lang/pt-BR/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Criar workspace', 'create_post' => 'Novo post', 'profile' => 'Perfil', + 'my_account' => 'Minha conta', + 'account_settings' => 'Conta e cobrança', 'log_out' => 'Sair', 'workspace' => 'Workspace: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Analytics', 'automations' => 'Automações', 'settings' => 'Configurações', + 'onboarding' => 'Primeiros passos', + 'onboarding_hint' => 'Complete a configuração', 'posts' => [ 'calendar' => 'Calendário', @@ -44,7 +48,9 @@ 'signatures' => 'Assinaturas', 'labels' => 'Etiquetas', 'assets' => 'Mídias', + 'settings' => 'Configurações', 'api_keys' => 'API Keys', + 'mcp' => 'MCP', ], 'notifications' => 'Notificações', diff --git a/lang/pt-BR/welcome.php b/lang/pt-BR/welcome.php new file mode 100644 index 000000000..3952b00fc --- /dev/null +++ b/lang/pt-BR/welcome.php @@ -0,0 +1,57 @@ + 'O que melhor descreve você?', + 'description' => 'Escolha a opção mais próxima e a gente personaliza sua experiência.', + 'continue' => 'Continuar', + 'checkout_owner_only' => 'Peça ao dono da conta para concluir o checkout e iniciar a assinatura.', + 'checkout_trial_note' => 'Teste grátis de :days dias, depois o :plan é cobrado mensalmente. Cancele quando quiser.', + 'checkout_plan_note' => 'Após o checkout, o :plan é cobrado mensalmente.', + 'subscription_required_title' => 'Aguardando o dono da conta', + 'subscription_required_description' => 'Esta conta ainda não tem uma assinatura ativa. Peça ao dono da conta para concluir o checkout — você terá acesso total assim que ela estiver ativa.', + 'subscription_required_owner' => 'O dono da sua conta é :name.', + 'progress' => 'Progresso do onboarding', + 'go_to_step' => 'Ir para a etapa :step', + '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', + ], + 'goals_title' => 'Qual o seu objetivo?', + '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' => 'Criar posts mais rápido com IA', + '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', + ], + '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', + ], +]; diff --git a/lang/ru/billing.php b/lang/ru/billing.php index c6e7d9255..873db85fe 100644 --- a/lang/ru/billing.php +++ b/lang/ru/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => 'Оформление отменено', 'cancelled_description' => 'Оформление отменено. Списаний не было.', 'retry' => 'Попробовать снова', + 'taking_long' => 'Это занимает больше времени, чем ожидалось, — подождите, мы всё ещё настраиваем аккаунт.', + 'live' => 'Live', ], ]; diff --git a/lang/ru/mcp.php b/lang/ru/mcp.php new file mode 100644 index 000000000..4f8acfde4 --- /dev/null +++ b/lang/ru/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'Подключите ИИ-ассистентов, чтобы они создавали и управляли постами в вашем аккаунте TryPost.', + 'step_add' => 'Вставьте имя, URL или config ниже в своё приложение. Вход откроется в браузере при первом подключении.', + 'name_label' => 'Имя', + 'url_label' => 'URL сервера', + 'config_label' => 'Config', + 'connected_title' => 'Подключённые приложения', + 'connected_description' => 'Ассистенты, вошедшие от имени кого угодно в этом аккаунте. Отключить можно только свои.', + 'connected_empty' => 'Пока ничего не подключено. Используйте Claude, ChatGPT или другого клиента выше.', + 'connected_by' => 'Подключил :name', + 'disconnect' => 'Отключить', + 'disconnect_title' => 'Отключить приложение', + 'disconnect_confirm' => 'Это выйдет из аккаунта TryPost в приложении. Нужно будет подключиться снова, чтобы снова использовать MCP.', + 'disconnected' => 'Приложение отключено.', + 'copied' => 'Скопировано', + 'last_used' => 'Последнее использование', + 'never' => 'Никогда', + 'documentation_title' => 'Документация', + 'documentation_description' => 'Гайды по клиентам, доступные tools и решение проблем.', + 'view_docs' => 'Открыть документацию', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Другие приложения', + 'other_clients_description' => 'Cursor, VS Code, Claude Code и всё, что говорит на MCP.', + + 'clients' => [ + 'cursor' => 'Добавьте TryPost как удалённый MCP-сервер в Cursor.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Вставьте конфиг ниже в настройки MCP VS Code.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Вставьте конфиг ниже в настройки MCP Claude Code.', + 'claude_code_name' => 'Claude Code', + 'other' => 'Работает с любым клиентом, который читает config mcpServers.', + 'other_name' => 'Другие', + ], +]; diff --git a/lang/ru/onboarding.php b/lang/ru/onboarding.php index d7b64adde..f85afa546 100644 --- a/lang/ru/onboarding.php +++ b/lang/ru/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'Добро пожаловать в TryPost', + 'description' => 'Выполните шаги ниже, чтобы увидеть, как работает TryPost, и опубликовать первый пост.', + 'skip' => 'Пропустить пока', + 'continue' => 'Перейти в TryPost', + 'status' => [ + 'complete' => 'Готово', + 'todo' => 'Сделать', ], - 'goals_title' => 'Какова ваша цель с TryPost?', - 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', - 'goals' => [ - 'save_time' => 'Экономить время, публикуя всюду сразу', - 'ai_content' => 'Создавать посты быстрее с помощью ИИ', - 'plan_calendar' => 'Планировать посты в календаре', - 'stay_on_brand' => 'Держать каждый пост в стиле бренда', - 'grow_audience' => 'Наращивать аудиторию и вовлечённость', - 'drive_sales' => 'Получать больше трафика и продаж', - 'manage_clients' => 'Управлять несколькими брендами или клиентами', - 'team_collaboration' => 'Работать с командой', - 'automate_api' => 'Автоматизировать публикацию с помощью API, MCP или кода', - 'track_performance' => 'Отслеживать эффективность постов', - 'just_exploring' => 'Пока просто знакомлюсь', - 'other' => 'Что-то ещё', + 'mcp' => [ + 'title' => 'Подключите ИИ-ассистента', + 'description' => 'Добавьте TryPost как MCP-сервер, чтобы ассистент мог создавать и вести соцпосты за вас.', + 'copy_step' => 'Скопируйте URL сервера TryPost', + 'open_step' => 'Откройте ИИ-ассистента', + 'copy' => 'Копировать URL', + 'copied' => 'URL MCP скопирован.', + 'connect' => 'Подключить через :client', + 'clients' => [ + 'claude' => 'Откройте Settings → Connectors, добавьте свой connector и вставьте URL выше.', + 'chatgpt' => 'Откройте Settings → Apps & Connectors, создайте свой connector и вставьте 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' => 'ИИ-ассистент (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/settings.php b/lang/ru/settings.php index 63b69024c..79cf9d15f 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -129,6 +129,7 @@ 'brand' => 'Бренд', 'users' => 'Участники', 'api_keys' => 'API-ключи', + 'mcp' => 'MCP', ], 'title' => 'Настройки рабочего пространства', 'logo_heading' => 'Логотип рабочего пространства', diff --git a/lang/ru/sidebar.php b/lang/ru/sidebar.php index 4ebffe4e7..bf18d1bb2 100644 --- a/lang/ru/sidebar.php +++ b/lang/ru/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => 'Создать рабочее пространство', 'create_post' => 'Создать пост', 'profile' => 'Профиль', + 'my_account' => 'Мой аккаунт', + 'account_settings' => 'Аккаунт и оплата', 'log_out' => 'Выйти', 'workspace' => 'Рабочее пространство: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Аналитика', 'automations' => 'Автоматизации', 'settings' => 'Настройки', + 'onboarding' => 'Начало работы', + 'onboarding_hint' => 'Завершите настройку', 'posts' => [ 'calendar' => 'Календарь', @@ -44,7 +48,9 @@ 'signatures' => 'Подписи', 'labels' => 'Метки', 'assets' => 'Медиафайлы', + 'settings' => 'Настройки', 'api_keys' => 'API-ключи', + 'mcp' => 'MCP', ], 'notifications' => 'Уведомления', diff --git a/lang/ru/welcome.php b/lang/ru/welcome.php new file mode 100644 index 000000000..73e4aa67b --- /dev/null +++ b/lang/ru/welcome.php @@ -0,0 +1,57 @@ + 'Что лучше всего вас описывает?', + 'description' => 'Выберите ближайший вариант — мы настроим опыт под вас.', + 'continue' => 'Продолжить', + 'checkout_owner_only' => 'Попросите владельца аккаунта завершить оплату и оформить подписку.', + 'checkout_trial_note' => 'Бесплатный пробный период :days дн., затем :plan оплачивается ежемесячно. Отмена в любое время.', + 'checkout_plan_note' => 'После оформления :plan оплачивается ежемесячно.', + 'subscription_required_title' => 'Ожидание владельца аккаунта', + 'subscription_required_description' => 'У этого аккаунта пока нет активной подписки. Попросите владельца завершить оплату — вы получите полный доступ сразу после её активации.', + 'subscription_required_owner' => 'Владелец вашего аккаунта — :name.', + 'progress' => 'Прогресс приветствия', + 'go_to_step' => 'Перейти к шагу :step', + 'personas' => [ + 'creator' => 'Автор контента', + 'freelancer' => 'Фрилансер', + 'developer' => 'Разработчик', + 'startup' => 'Стартап', + 'agency' => 'Агентство', + 'small_business' => 'Малый бизнес', + 'marketer' => 'Маркетолог', + 'online_store' => 'Интернет-магазин', + 'other' => 'Другое', + ], + 'goals_title' => 'Какова ваша цель?', + 'goals_description' => 'Выберите всё, что подходит, и мы настроим TryPost для вас.', + 'goals' => [ + 'save_time' => 'Экономить время, публикуя всюду сразу', + 'ai_content' => 'Создавать посты быстрее с помощью ИИ', + 'plan_calendar' => 'Планировать посты в календаре', + 'stay_on_brand' => 'Держать каждый пост в стиле бренда', + 'grow_audience' => 'Наращивать аудиторию и вовлечённость', + 'drive_sales' => 'Получать больше трафика и продаж', + 'manage_clients' => 'Управлять несколькими брендами или клиентами', + 'just_exploring' => 'Пока просто знакомлюсь', + 'other' => 'Что-то ещё', + ], + 'referral_source_title' => 'Как вы нас нашли?', + 'referral_source_description' => 'Это помогает нам понять, как люди узнают о TryPost.', + 'referral_source' => [ + 'google' => 'Google или поиск', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram или Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'ИИ-ассистент (ChatGPT, Claude…)', + 'friend' => 'Друг или коллега', + 'blog' => 'Блог, рассылка или статья', + 'other' => 'Что-то другое', + ], +]; diff --git a/lang/tr/billing.php b/lang/tr/billing.php index e50e65562..780329114 100644 --- a/lang/tr/billing.php +++ b/lang/tr/billing.php @@ -74,5 +74,7 @@ 'cancelled_title' => 'Ödeme iptal edildi', 'cancelled_description' => 'Ödemeniz iptal edildi. Herhangi bir ücret alınmadı.', 'retry' => 'Tekrar dene', + 'taking_long' => 'Bu beklenenden uzun sürüyor — bekleyin, kurulum hâlâ devam ediyor.', + 'live' => 'Canlı', ], ]; diff --git a/lang/tr/mcp.php b/lang/tr/mcp.php new file mode 100644 index 000000000..c39fb668b --- /dev/null +++ b/lang/tr/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => 'TryPost hesabınızla gönderi oluşturup yönetmeleri için yapay zeka asistanlarını bağlayın.', + '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', + 'config_label' => 'Config', + 'connected_title' => 'Bağlı uygulamalar', + 'connected_description' => 'Bu hesaptaki herhangi birinin giriş yaptığı asistanlar. Yalnızca kendininkileri bağlantıyı kesebilirsiniz.', + 'connected_empty' => 'Henüz bağlı bir şey yok. Yukarıdan Claude, ChatGPT veya başka bir istemci kullanın.', + 'connected_by' => ':name tarafından bağlandı', + 'disconnect' => 'Bağlantıyı kes', + 'disconnect_title' => 'Uygulama bağlantısını kes', + 'disconnect_confirm' => 'Bu, uygulamayı TryPost’tan çıkarır. MCP’yi yeniden kullanmak için tekrar bağlanması gerekir.', + 'disconnected' => 'Uygulama bağlantısı kesildi.', + 'copied' => 'Kopyalandı', + 'last_used' => 'Son kullanım', + 'never' => 'Hiç', + 'documentation_title' => 'Dokümantasyon', + 'documentation_description' => 'İstemci kurulum rehberleri, kullanılabilir tools ve sorun giderme.', + 'view_docs' => 'Dokümantasyonu görüntüle', + 'connector_name' => 'TryPost', + + 'other_clients_title' => 'Diğer uygulamalar', + 'other_clients_description' => 'Cursor, VS Code, Claude Code ve MCP konuşan diğer her şey.', + + 'clients' => [ + 'cursor' => 'Cursor’da TryPost’u uzak MCP sunucusu olarak ekleyin.', + 'cursor_name' => 'Cursor', + 'vscode' => 'Aşağıdaki yapılandırmayı VS Code\'un MCP ayarlarına yapıştırın.', + 'vscode_name' => 'VS Code', + 'claude_code' => 'Aşağıdaki yapılandırmayı Claude Code\'un MCP ayarlarına yapıştırın.', + 'claude_code_name' => 'Claude Code', + 'other' => 'mcpServers config okuyan her istemciyle çalışır.', + 'other_name' => 'Diğer', + ], +]; diff --git a/lang/tr/onboarding.php b/lang/tr/onboarding.php index b54630a4c..f6e0389b7 100644 --- a/lang/tr/onboarding.php +++ b/lang/tr/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => 'TryPost’a hoş geldin', + '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' => 'Şimdilik atla', + 'continue' => 'TryPost’a devam et', + 'status' => [ + 'complete' => 'Tamamlandı', + 'todo' => 'Yapılacak', ], - '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' => 'AI ile daha hızlı 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', - 'team_collaboration' => 'Ekibimle çalışmak', - 'automate_api' => 'API, MCP veya kodla paylaşımı otomatikleştirmek', - 'track_performance' => 'Gönderilerimin performansını görmek', - '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.', + 'copy_step' => 'TryPost sunucu URL’ini kopyala', + 'open_step' => 'AI asistanını aç', + 'copy' => 'URL’yi kopyala', + 'copied' => 'MCP URL’si kopyalandı.', + 'connect' => ':client ile bağlan', + 'clients' => [ + 'claude' => 'Settings → Connectors’ı aç, özel bir connector ekle ve yukarıdaki URL’yi yapıştır.', + 'chatgpt' => 'Settings → Apps & Connectors’ı aç, özel bir connector oluştur ve yukarıdaki URL’yi yapıştır.', + ], ], - '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/settings.php b/lang/tr/settings.php index 359d60eff..f2852da05 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -131,6 +131,7 @@ 'brand' => 'Marka', 'users' => 'Üyeler', 'api_keys' => 'API Anahtarları', + 'mcp' => 'MCP', ], 'title' => 'Çalışma alanı ayarları', 'logo_heading' => 'Çalışma alanı logosu', diff --git a/lang/tr/sidebar.php b/lang/tr/sidebar.php index c7936fc96..393710fce 100644 --- a/lang/tr/sidebar.php +++ b/lang/tr/sidebar.php @@ -8,6 +8,8 @@ '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', 'log_out' => 'Çıkış yap', 'workspace' => 'Çalışma alanı: :name', @@ -30,6 +32,8 @@ 'analytics' => 'Analitik', 'automations' => 'Otomasyonlar', 'settings' => 'Ayarlar', + 'onboarding' => 'Başlarken', + 'onboarding_hint' => 'Kurulumu bitir', 'posts' => [ 'calendar' => 'Takvim', @@ -44,7 +48,9 @@ 'signatures' => 'İmzalar', 'labels' => 'Etiketler', 'assets' => 'Varlıklar', + 'settings' => 'Ayarlar', 'api_keys' => 'API Anahtarları', + 'mcp' => 'MCP', ], 'notifications' => 'Bildirimler', diff --git a/lang/tr/welcome.php b/lang/tr/welcome.php new file mode 100644 index 000000000..b43c1b415 --- /dev/null +++ b/lang/tr/welcome.php @@ -0,0 +1,57 @@ + 'Sizi en iyi ne tanımlar?', + 'description' => 'En yakın seçeneği seçin, deneyiminizi kişiselleştirelim.', + 'continue' => 'Devam et', + 'checkout_owner_only' => 'Hesap sahibinden ödemeyi tamamlayıp aboneliği başlatmasını isteyin.', + 'checkout_trial_note' => ':days gün ücretsiz deneme, sonra :plan aylık faturalandırılır. İstediğiniz zaman iptal edin.', + 'checkout_plan_note' => 'Ödeme sonrası :plan aylık faturalandırılır.', + 'subscription_required_title' => 'Hesap sahibi bekleniyor', + 'subscription_required_description' => 'Bu hesabın henüz etkin bir aboneliği yok. Hesap sahibinden ödemeyi tamamlamasını isteyin — abonelik etkinleşir etkinleşmez tam erişiminiz olur.', + 'subscription_required_owner' => 'Hesap sahibiniz :name.', + 'progress' => 'Karşılama ilerlemesi', + 'go_to_step' => ':step. adıma git', + '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', + ], + 'goals_title' => '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' => 'AI ile daha hızlı 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', + ], + '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', + ], +]; diff --git a/lang/zh/billing.php b/lang/zh/billing.php index c17249c33..73e74b582 100644 --- a/lang/zh/billing.php +++ b/lang/zh/billing.php @@ -72,5 +72,7 @@ 'cancelled_title' => '结账已取消', 'cancelled_description' => '你的结账已取消,未产生任何费用。', 'retry' => '重试', + 'taking_long' => '耗时比预期更长 — 请稍候,我们仍在为您设置。', + 'live' => '实时', ], ]; diff --git a/lang/zh/mcp.php b/lang/zh/mcp.php new file mode 100644 index 000000000..66addb6e4 --- /dev/null +++ b/lang/zh/mcp.php @@ -0,0 +1,41 @@ + 'MCP', + 'subtitle' => '连接 AI 助手,让它们用你的 TryPost 账户创建和管理帖子。', + 'step_add' => '将下方的名称、URL 或配置粘贴到你的应用中。首次连接时会在浏览器中打开登录。', + 'name_label' => '名称', + 'url_label' => '服务器 URL', + 'config_label' => '配置', + 'connected_title' => '已连接的应用', + 'connected_description' => '此账户中任何人已登录的助手。你只能断开自己的连接。', + 'connected_empty' => '还没有连接。请使用上方的 Claude、ChatGPT 或其他客户端。', + 'connected_by' => '由 :name 连接', + 'disconnect' => '断开连接', + 'disconnect_title' => '断开应用', + 'disconnect_confirm' => '这将使应用退出 TryPost。再次使用 MCP 前需要重新连接。', + 'disconnected' => '应用已断开连接。', + 'copied' => '已复制', + 'last_used' => '最近使用', + 'never' => '从未', + 'documentation_title' => '文档', + 'documentation_description' => '各客户端设置指南、可用工具和问题排查。', + 'view_docs' => '查看文档', + 'connector_name' => 'TryPost', + + 'other_clients_title' => '其他应用', + 'other_clients_description' => 'Cursor、VS Code、Claude Code 以及任何支持 MCP 的应用。', + + 'clients' => [ + 'cursor' => '在 Cursor 中将 TryPost 添加为远程 MCP 服务器。', + 'cursor_name' => 'Cursor', + 'vscode' => '将下方配置粘贴到 VS Code 的 MCP 设置中。', + 'vscode_name' => 'VS Code', + 'claude_code' => '将下方配置粘贴到 Claude Code 的 MCP 设置中。', + 'claude_code_name' => 'Claude Code', + 'other' => '适用于任何读取 mcpServers 配置的客户端。', + 'other_name' => '其他', + ], +]; diff --git a/lang/zh/onboarding.php b/lang/zh/onboarding.php index 9761ce99a..3d58cf6a7 100644 --- a/lang/zh/onboarding.php +++ b/lang/zh/onboarding.php @@ -3,55 +3,46 @@ 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', + 'welcome_anonymous' => '欢迎使用 TryPost', + 'description' => '按下面的步骤了解 TryPost 的用法,并发布你的第一条内容。', + 'skip' => '暂时跳过', + 'continue' => '继续前往 TryPost', + 'status' => [ + 'complete' => '已完成', + 'todo' => '待完成', ], - 'goals_title' => '你使用 TryPost 的目标是什么?', - 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', - 'goals' => [ - 'save_time' => '一次发布到所有平台,节省时间', - 'ai_content' => '借助 AI 更快地创建帖子', - 'plan_calendar' => '在日历上规划我的帖子', - 'stay_on_brand' => '让每一条帖子都符合品牌调性', - 'grow_audience' => '增长我的受众和互动', - 'drive_sales' => '获得更多流量和销量', - 'manage_clients' => '管理多个品牌或客户', - 'team_collaboration' => '与我的团队协作', - 'automate_api' => '通过 API、MCP 或代码自动发帖', - 'track_performance' => '查看我的帖子表现', - 'just_exploring' => '目前只是随便看看', - 'other' => '其他需求', + 'mcp' => [ + 'title' => '连接你的 AI 助手', + 'description' => '将 TryPost 添加为 MCP 服务器,让助手帮你创建和管理社交内容。', + 'copy_step' => '复制你的 TryPost 服务器 URL', + 'open_step' => '打开你的 AI 助手', + 'copy' => '复制 URL', + 'copied' => '已复制 MCP URL。', + 'connect' => '使用 :client 连接', + 'clients' => [ + 'claude' => '打开 Settings → Connectors,添加自定义连接器,然后粘贴上方 URL。', + 'chatgpt' => '打开 Settings → Apps & Connectors,创建自定义连接器,然后粘贴上方 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/settings.php b/lang/zh/settings.php index 314d8a49e..e830106c5 100644 --- a/lang/zh/settings.php +++ b/lang/zh/settings.php @@ -129,6 +129,7 @@ 'brand' => '品牌', 'users' => '成员', 'api_keys' => 'API 密钥', + 'mcp' => 'MCP', ], 'title' => '工作区设置', 'logo_heading' => '工作区徽标', diff --git a/lang/zh/sidebar.php b/lang/zh/sidebar.php index 46a3eda6f..3a9389b81 100644 --- a/lang/zh/sidebar.php +++ b/lang/zh/sidebar.php @@ -8,6 +8,8 @@ 'create_workspace' => '创建工作区', 'create_post' => '创建帖子', 'profile' => '个人资料', + 'my_account' => '我的账户', + 'account_settings' => '账户与账单', 'log_out' => '退出登录', 'workspace' => '工作区::name', @@ -30,6 +32,8 @@ 'analytics' => '分析', 'automations' => '自动化', 'settings' => '设置', + 'onboarding' => '开始使用', + 'onboarding_hint' => '完成设置', 'posts' => [ 'calendar' => '日历', @@ -44,7 +48,9 @@ 'signatures' => '签名', 'labels' => '标签', 'assets' => '素材库', + 'settings' => '设置', 'api_keys' => 'API 密钥', + 'mcp' => 'MCP', ], 'notifications' => '通知', diff --git a/lang/zh/welcome.php b/lang/zh/welcome.php new file mode 100644 index 000000000..1704c6b0c --- /dev/null +++ b/lang/zh/welcome.php @@ -0,0 +1,57 @@ + '哪项最能描述你?', + 'description' => '选择最接近的一项,我们会为你定制体验。', + 'continue' => '继续', + 'checkout_owner_only' => '请让账户所有者完成结账并开始订阅。', + 'checkout_trial_note' => '免费试用 :days 天,之后 :plan 按月计费。可随时取消。', + 'checkout_plan_note' => '结账后 :plan 将按月计费。', + 'subscription_required_title' => '等待账户所有者', + 'subscription_required_description' => '此账户还没有有效订阅。请让账户所有者完成结账 — 订阅生效后您即可获得完整访问权限。', + 'subscription_required_owner' => '您的账户所有者是 :name。', + 'progress' => '欢迎进度', + 'go_to_step' => '前往第 :step 步', + 'personas' => [ + 'creator' => '内容创作者', + 'freelancer' => '自由职业者', + 'developer' => '开发者', + 'startup' => '初创公司', + 'agency' => '代理机构', + 'small_business' => '小型企业', + 'marketer' => '营销人员', + 'online_store' => '网店', + 'other' => '其他', + ], + 'goals_title' => '你的目标是什么?', + 'goals_description' => '选择所有符合的选项,我们会为你配置好 TryPost。', + 'goals' => [ + 'save_time' => '一次发布到所有平台,节省时间', + 'ai_content' => '借助 AI 更快地创建帖子', + 'plan_calendar' => '在日历上规划我的帖子', + 'stay_on_brand' => '让每一条帖子都符合品牌调性', + 'grow_audience' => '增长我的受众和互动', + 'drive_sales' => '获得更多流量和销量', + 'manage_clients' => '管理多个品牌或客户', + 'just_exploring' => '目前只是随便看看', + 'other' => '其他需求', + ], + 'referral_source_title' => '您是如何找到我们的?', + 'referral_source_description' => '这有助于我们了解人们是如何发现 TryPost 的。', + 'referral_source' => [ + 'google' => 'Google 或搜索', + 'x' => 'X (Twitter)', + 'linkedin' => 'LinkedIn', + 'youtube' => 'YouTube', + 'tiktok' => 'TikTok', + 'instagram' => 'Instagram 或 Threads', + 'reddit' => 'Reddit', + 'product_hunt' => 'Product Hunt', + 'ai_assistant' => 'AI 助手(ChatGPT、Claude 等)', + 'friend' => '朋友或同事', + 'blog' => '博客、新闻通讯或文章', + 'other' => '其他', + ], +]; diff --git a/public/images/ai/chatgpt-black.svg b/public/images/ai/chatgpt-black.svg new file mode 100644 index 000000000..9c80705e5 --- /dev/null +++ b/public/images/ai/chatgpt-black.svg @@ -0,0 +1 @@ +ChatGPT \ No newline at end of file diff --git a/public/images/ai/chatgpt-white.svg b/public/images/ai/chatgpt-white.svg new file mode 100644 index 000000000..fb4d0ac4d --- /dev/null +++ b/public/images/ai/chatgpt-white.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/public/images/ai/claude.svg b/public/images/ai/claude.svg new file mode 100644 index 000000000..5d8d7461d --- /dev/null +++ b/public/images/ai/claude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/ai/cursor.svg b/public/images/ai/cursor.svg new file mode 100644 index 000000000..a1bb0420c --- /dev/null +++ b/public/images/ai/cursor.svg @@ -0,0 +1,6 @@ + + + diff --git a/public/images/ai/other-clients.svg b/public/images/ai/other-clients.svg new file mode 100644 index 000000000..c9136d9f5 --- /dev/null +++ b/public/images/ai/other-clients.svg @@ -0,0 +1,6 @@ + + + diff --git a/public/images/ai/vscode.svg b/public/images/ai/vscode.svg new file mode 100644 index 000000000..73df1aefb --- /dev/null +++ b/public/images/ai/vscode.svg @@ -0,0 +1,6 @@ + + + diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 7aea78e49..b459bf2d7 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -1,5 +1,5 @@ \ No newline at end of file + diff --git a/resources/js/components/NavUser.vue b/resources/js/components/NavUser.vue deleted file mode 100644 index 8253dbf42..000000000 --- a/resources/js/components/NavUser.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/resources/js/components/NotificationBell.vue b/resources/js/components/NotificationBell.vue index 7fcd5aab1..6fd7a65df 100644 --- a/resources/js/components/NotificationBell.vue +++ b/resources/js/components/NotificationBell.vue @@ -1,10 +1,18 @@