diff --git a/AGENTS.md b/AGENTS.md index fbbcec264..7184a16f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -333,3 +333,118 @@ to `Connected`, because it does so after a real `verify()` call. A successful token refresh is not that proof — the refresh token being valid says nothing about whether publishing still works — so `RefreshSocialToken` must not promote, even though it would let a paused repurpose resume sooner. + +## UI locale (`users.locale`) + +The user's UI language lives in the database, on `users.locale`, cast to +`App\Enums\User\Locale`. That enum is the single source of truth for the +supported locales — there is no `config/languages.php` any more, and a case is +only valid if `lang/` exists (`LocalizationParityTest` enforces both that +and parity with `ContentLanguage`). + +- **There is no `locale` cookie.** The app stored the locale in the database + until March 2026, moved it to a forever cookie, and moved it back here. Do not + reintroduce the cookie: a second source of truth is what made the switcher and + the register page disagree the first time. +- **`SetLocale` has exactly one rule:** an authenticated request renders in + `Auth::user()->locale`, everything else in `Locale::DEFAULT`. It does not look + at the request body, old input or `Accept-Language`. A logged-out visitor + therefore always gets English from the server, including validation messages. +- **The auth switcher is client-side only.** It calls `loadLanguageAsync`, so + changing language on login or register costs no round trip and touches nothing + on the server. `useGuestLocale` holds the choice at module scope so it survives + Inertia navigation between those screens, and it sets `document.documentElement.dir` + from the picked language — for a guest that is the *only* source of direction, + since the middleware renders `htmlDir` from the default on every request. +- **Register submits `locale` as a required hidden field** and creates the user + with it. **Login submits it only once the visitor picks a language** — the + field goes out empty otherwise, and an empty value leaves `users.locale` + untouched. This asymmetry is load-bearing: the login screen always renders in + `Locale::DEFAULT`, so an always-sent field would reset every non-English user + to English on each login. Forgot and reset password do not send it at all. +- Google and GitHub signups store `Locale::DEFAULT`: they have no picker, and the + OAuth callback tells you nothing reliable about the person. + +## PostHog person properties + +`App\Jobs\PostHog\SyncUser` is the only place that writes person properties, and +the distinction between its two buckets is load-bearing: + +- **`$set_once`** — first-touch facts that must never be rewritten: `signed_up_at` + and the attribution keys (`utm_*`, `gclid`, `fbclid`, …). A later sync must not + overwrite where a user originally came from. +- **Top level** — current state, overwritten on every sync: `$email`, `$name`, and + `locale`. + +`locale` mirrors `users.locale` and is what the PostHog email automations (the +onboarding cadence and friends) read to decide which translation to send, so it +has to reflect the language the user picked *now* — never `$set_once`. Anything +that changes `users.locale` must dispatch `SyncUser`; `ProfileController@updateLanguage` +does, and registration already does via `CreateUser`. + +Do not reach for `$browser_language` / `$browser_language_prefix` instead. They +are captured automatically by posthog-js but only as **event** properties on +`$pageview`, so they cannot segment a person or feed an automation — and they +report the browser's language at that pageview, not the language the user chose. + +Before adding a person property, check what the project already has with the +PostHog MCP (`read-data-schema` with `{"kind": "entity_properties", "entity": +"person"}`) rather than guessing a name; overwriting an existing property is +silent and retroactive. + +## Emails (Maizzle + i18n) + +**Every email the app sends is fully translated into all 16 supported locales, +and every new email must be too.** There is no English-only email left in the +codebase, and adding one is a regression — not a gap to fill in later. + +**Every email is built with Maizzle, and every string in it goes through +`__()`.** Both halves are mandatory, with no exceptions for "small", +"transactional", "internal" or "temporary" emails: + +- **Maizzle, always.** Email HTML is authored in `maizzle/templates/.html` + and compiled to `resources/views/mail/.blade.php` by + `cd maizzle && npm run build`. Never hand-write a Blade view under + `resources/views/mail/`, never use Laravel's markdown mailables, and **never + edit the Blade files** — they are build output and the next build overwrites + them. A one-off email written outside Maizzle loses the shared layout, header, + footer and inlined CSS, and silently drops out of the translation workflow. +- **i18n, always.** No user-visible string may be a literal — not in the + template, not in the Mailable, not in a notification closure. Subject, preview + text, headings, body copy, button labels and footer chrome all resolve through + `__()` / `trans_choice()` against `lang/*/mail.php`, in all 16 locales. A + literal is invisible to `LocalizationParityTest`, so it ships and stays broken. + +How that works in practice: + +- **Maizzle eats one `{`-level.** Write `@{{ ... }}` in the template to emit Blade + `{{ ... }}`; write `{!! ... !!}` as-is (it passes through via + `posthtml.expressions.unescapeDelimiters`). A `{{ }}` written directly is + evaluated by Maizzle at build time and disappears. +- **Copy lives in the template, not in the Mailable.** Body text is + `@{{ __('mail..') }}` inside the template; the Mailable resolves only + the envelope metadata the layout needs — `subject`, `title`, `previewText` — and + otherwise passes **data** (`$workspaceName`, `$endpoint`, `$publishedPlatforms`), + never sentences. Injecting resolved strings as view variables is what the + disconnected-connections email used to do, and it meant every new sentence had + to be threaded through PHP while the template gave no hint it was translatable. +- **One `lang/*/mail.php` block per template**, keyed by the slug with dashes as + underscores (`post-published.html` => `post_published`). Shared chrome (footer + tagline, sign-off) lives under `layout`. Keys go in all 16 locales; + `LocalizationParityTest` fails on drift. Feature lang files must not carry email + copy — `webhooks.mail.*` moved here for that reason. +- **The recipient's locale is automatic.** `User` implements + `HasLocalePreference`, so `Mail::to($user)` and `$user->notify(...)` localize on + their own; never add a `->locale()` call at a send site. Two consequences: + `Mail::to($user->email)` (a bare string) silently loses it, so always pass the + model; and the invite is the one exception — the recipient has no account yet, + so `CreateInvite` explicitly sends in the inviter's locale. +- **`trans_choice` must handle zero.** The last plural segment is `[0,*]`, not + `[2,*]`: `PostAtRisk` can report a count of 0 when rows disappear between + dispatch and send, and an unmatched count renders a stray leading space. + +New email checklist: add the template, add the `mail.` block to all 16 +locales, write a Mailable that passes data plus the three metadata strings, send +with `Mail::to($user)`, run the Maizzle build, and cover it with a render test — +`tests/Feature/Mail/MailRenderingTest.php` exists because copy moving into the +view turns a forgotten variable into a runtime-only failure. diff --git a/CLAUDE.md b/CLAUDE.md index 0824a34c5..7605cc7aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -260,6 +260,121 @@ One connected identity per social network per workspace is the Cloud default. Th Self-hosted compose / `.env.example` set this `true`. When the env is unset, the config falls back to `SELF_HOSTED` so existing self-hosted installs keep multiple accounts. Do **not** use `selfHosted` for the occupancy check (observer, Telegram connect, `NetworkConnectGrid`). +## UI locale (`users.locale`) + +The user's UI language lives in the database, on `users.locale`, cast to +`App\Enums\User\Locale`. That enum is the single source of truth for the +supported locales — there is no `config/languages.php` any more, and a case is +only valid if `lang/` exists (`LocalizationParityTest` enforces both that +and parity with `ContentLanguage`). + +- **There is no `locale` cookie.** The app stored the locale in the database + until March 2026, moved it to a forever cookie, and moved it back here. Do not + reintroduce the cookie: a second source of truth is what made the switcher and + the register page disagree the first time. +- **`SetLocale` has exactly one rule:** an authenticated request renders in + `Auth::user()->locale`, everything else in `Locale::DEFAULT`. It does not look + at the request body, old input or `Accept-Language`. A logged-out visitor + therefore always gets English from the server, including validation messages. +- **The auth switcher is client-side only.** It calls `loadLanguageAsync`, so + changing language on login or register costs no round trip and touches nothing + on the server. `useGuestLocale` holds the choice at module scope so it survives + Inertia navigation between those screens, and it sets `document.documentElement.dir` + from the picked language — for a guest that is the *only* source of direction, + since the middleware renders `htmlDir` from the default on every request. +- **Register submits `locale` as a required hidden field** and creates the user + with it. **Login submits it only once the visitor picks a language** — the + field goes out empty otherwise, and an empty value leaves `users.locale` + untouched. This asymmetry is load-bearing: the login screen always renders in + `Locale::DEFAULT`, so an always-sent field would reset every non-English user + to English on each login. Forgot and reset password do not send it at all. +- Google and GitHub signups store `Locale::DEFAULT`: they have no picker, and the + OAuth callback tells you nothing reliable about the person. + +## PostHog person properties + +`App\Jobs\PostHog\SyncUser` is the only place that writes person properties, and +the distinction between its two buckets is load-bearing: + +- **`$set_once`** — first-touch facts that must never be rewritten: `signed_up_at` + and the attribution keys (`utm_*`, `gclid`, `fbclid`, …). A later sync must not + overwrite where a user originally came from. +- **Top level** — current state, overwritten on every sync: `$email`, `$name`, and + `locale`. + +`locale` mirrors `users.locale` and is what the PostHog email automations (the +onboarding cadence and friends) read to decide which translation to send, so it +has to reflect the language the user picked *now* — never `$set_once`. Anything +that changes `users.locale` must dispatch `SyncUser`; `ProfileController@updateLanguage` +does, and registration already does via `CreateUser`. + +Do not reach for `$browser_language` / `$browser_language_prefix` instead. They +are captured automatically by posthog-js but only as **event** properties on +`$pageview`, so they cannot segment a person or feed an automation — and they +report the browser's language at that pageview, not the language the user chose. + +Before adding a person property, check what the project already has with the +PostHog MCP (`read-data-schema` with `{"kind": "entity_properties", "entity": +"person"}`) rather than guessing a name; overwriting an existing property is +silent and retroactive. + +## Emails (Maizzle + i18n) + +**Every email the app sends is fully translated into all 16 supported locales, +and every new email must be too.** There is no English-only email left in the +codebase, and adding one is a regression — not a gap to fill in later. + +**Every email is built with Maizzle, and every string in it goes through +`__()`.** Both halves are mandatory, with no exceptions for "small", +"transactional", "internal" or "temporary" emails: + +- **Maizzle, always.** Email HTML is authored in `maizzle/templates/.html` + and compiled to `resources/views/mail/.blade.php` by + `cd maizzle && npm run build`. Never hand-write a Blade view under + `resources/views/mail/`, never use Laravel's markdown mailables, and **never + edit the Blade files** — they are build output and the next build overwrites + them. A one-off email written outside Maizzle loses the shared layout, header, + footer and inlined CSS, and silently drops out of the translation workflow. +- **i18n, always.** No user-visible string may be a literal — not in the + template, not in the Mailable, not in a notification closure. Subject, preview + text, headings, body copy, button labels and footer chrome all resolve through + `__()` / `trans_choice()` against `lang/*/mail.php`, in all 16 locales. A + literal is invisible to `LocalizationParityTest`, so it ships and stays broken. + +How that works in practice: + +- **Maizzle eats one `{`-level.** Write `@{{ ... }}` in the template to emit Blade + `{{ ... }}`; write `{!! ... !!}` as-is (it passes through via + `posthtml.expressions.unescapeDelimiters`). A `{{ }}` written directly is + evaluated by Maizzle at build time and disappears. +- **Copy lives in the template, not in the Mailable.** Body text is + `@{{ __('mail..') }}` inside the template; the Mailable resolves only + the envelope metadata the layout needs — `subject`, `title`, `previewText` — and + otherwise passes **data** (`$workspaceName`, `$endpoint`, `$publishedPlatforms`), + never sentences. Injecting resolved strings as view variables is what the + disconnected-connections email used to do, and it meant every new sentence had + to be threaded through PHP while the template gave no hint it was translatable. +- **One `lang/*/mail.php` block per template**, keyed by the slug with dashes as + underscores (`post-published.html` => `post_published`). Shared chrome (footer + tagline, sign-off) lives under `layout`. Keys go in all 16 locales; + `LocalizationParityTest` fails on drift. Feature lang files must not carry email + copy — `webhooks.mail.*` moved here for that reason. +- **The recipient's locale is automatic.** `User` implements + `HasLocalePreference`, so `Mail::to($user)` and `$user->notify(...)` localize on + their own; never add a `->locale()` call at a send site. Two consequences: + `Mail::to($user->email)` (a bare string) silently loses it, so always pass the + model; and the invite is the one exception — the recipient has no account yet, + so `CreateInvite` explicitly sends in the inviter's locale. +- **`trans_choice` must handle zero.** The last plural segment is `[0,*]`, not + `[2,*]`: `PostAtRisk` can report a count of 0 when rows disappear between + dispatch and send, and an unmatched count renders a stray leading space. + +New email checklist: add the template, add the `mail.` block to all 16 +locales, write a Mailable that passes data plus the three metadata strings, send +with `Mail::to($user)`, run the Maizzle build, and cover it with a render test — +`tests/Feature/Mail/MailRenderingTest.php` exists because copy moving into the +view turns a forgotten variable into a runtime-only failure. + ## Icons (@tabler/icons-vue) - This project uses `@tabler/icons-vue` for all icons. NEVER use `lucide-vue-next`. diff --git a/app/Actions/Invite/CreateInvite.php b/app/Actions/Invite/CreateInvite.php index 33cbd65ef..ecbc1c254 100644 --- a/app/Actions/Invite/CreateInvite.php +++ b/app/Actions/Invite/CreateInvite.php @@ -14,15 +14,19 @@ class CreateInvite { public static function execute(Workspace $workspace, array $data): Invite { + $inviter = auth()->user(); + $invite = Invite::create([ 'account_id' => $workspace->account_id, - 'invited_by' => auth()->id(), + 'invited_by' => $inviter->id, 'email' => data_get($data, 'email'), 'role' => WorkspaceRole::from(data_get($data, 'role')), 'workspaces' => [$workspace->id], ]); - Mail::to($invite->email)->send(new WorkspaceInviteMail($invite)); + Mail::to($invite->email) + ->locale($inviter->preferredLocale()) + ->send(new WorkspaceInviteMail($invite)); return $invite; } diff --git a/app/Actions/User/CreateUser.php b/app/Actions/User/CreateUser.php index d3a5303f1..597bdd6c9 100644 --- a/app/Actions/User/CreateUser.php +++ b/app/Actions/User/CreateUser.php @@ -7,6 +7,7 @@ use App\Actions\Workspace\CreateWorkspace; use App\Enums\Plan\Slug; use App\Enums\PostHog\UserEvent; +use App\Enums\User\Locale; use App\Jobs\PostHog\SyncUser; use App\Models\Account; use App\Models\Plan; @@ -17,7 +18,7 @@ class CreateUser { /** - * @param array{name: string, email: string, password?: string, google_id?: string, github_id?: string, email_verified_at?: \DateTimeInterface|null, is_invite?: bool, registration_ip?: string|null} $data + * @param array{name: string, email: string, password?: string, google_id?: string, github_id?: string, email_verified_at?: \DateTimeInterface|null, is_invite?: bool, registration_ip?: string|null, locale?: string} $data * @param array $attributionParameters UTM parameters and ad click IDs (gclid, fbclid, etc.) captured before signup */ public static function execute(array $data, array $attributionParameters = []): User @@ -47,6 +48,7 @@ public static function execute(array $data, array $attributionParameters = []): 'email_verified_at' => data_get($data, 'email_verified_at', $isInviteRegistration ? now() : null), 'account_id' => $account->id, 'registration_ip' => data_get($data, 'registration_ip'), + 'locale' => Locale::from(data_get($data, 'locale', Locale::DEFAULT->value)), ], $attributionParameters)); $account->update(['owner_id' => $user->id]); diff --git a/app/Enums/User/Locale.php b/app/Enums/User/Locale.php new file mode 100644 index 000000000..b3655ff54 --- /dev/null +++ b/app/Enums/User/Locale.php @@ -0,0 +1,100 @@ + 'English', + self::Ukrainian => 'Українська', + self::PortugueseBrazil => 'Português', + self::Spanish => 'Español', + self::French => 'Français', + self::German => 'Deutsch', + self::Italian => 'Italiano', + self::Dutch => 'Nederlands', + self::Polish => 'Polski', + self::Greek => 'Ελληνικά', + self::Japanese => '日本語', + self::Korean => '한국어', + self::Chinese => '中文', + self::Russian => 'Русский', + self::Turkish => 'Türkçe', + self::Arabic => 'العربية', + }; + } + + public function flag(): string + { + return match ($this) { + self::English => 'US', + self::Ukrainian => 'UA', + self::PortugueseBrazil => 'BR', + self::Spanish => 'ES', + self::French => 'FR', + self::German => 'DE', + self::Italian => 'IT', + self::Dutch => 'NL', + self::Polish => 'PL', + self::Greek => 'GR', + self::Japanese => 'JP', + self::Korean => 'KR', + self::Chinese => 'CN', + self::Russian => 'RU', + self::Turkish => 'TR', + self::Arabic => 'SA', + }; + } + + public function direction(): string + { + return $this === self::Arabic ? 'rtl' : 'ltr'; + } + + /** + * @return array + */ + public static function values(): array + { + return array_map(fn (self $locale) => $locale->value, self::cases()); + } + + /** + * @return array + */ + public static function options(): array + { + return array_map( + fn (self $locale) => [ + 'code' => $locale->value, + 'name' => $locale->label(), + 'dir' => $locale->direction(), + 'flag' => asset("images/flags/{$locale->flag()}.svg"), + ], + self::cases(), + ); + } +} diff --git a/app/Http/Controllers/App/Settings/ProfileController.php b/app/Http/Controllers/App/Settings/ProfileController.php index 906b97942..6f9819e5b 100644 --- a/app/Http/Controllers/App/Settings/ProfileController.php +++ b/app/Http/Controllers/App/Settings/ProfileController.php @@ -8,6 +8,10 @@ use App\Http\Controllers\Controller; use App\Http\Requests\App\Settings\ProfileDeleteRequest; use App\Http\Requests\App\Settings\ProfileUpdateRequest; +use App\Http\Requests\App\Settings\UpdateLanguageRequest; +use App\Http\Requests\App\Settings\UploadPhotoRequest; +use App\Jobs\PostHog\SyncUser; +use App\Services\PostHogService; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -40,12 +44,8 @@ public function update(ProfileUpdateRequest $request): RedirectResponse return to_route('app.profile.edit'); } - public function uploadPhoto(Request $request): RedirectResponse + public function uploadPhoto(UploadPhotoRequest $request): RedirectResponse { - $request->validate([ - 'photo' => ['required', 'image', 'max:2048'], - ]); - $user = $request->user(); $user->clearMediaCollection('avatar'); $user->addMedia($request->file('photo'), 'avatar'); @@ -69,15 +69,15 @@ public function deletePhoto(Request $request): RedirectResponse return back(); } - public function updateLanguage(Request $request): RedirectResponse + public function updateLanguage(UpdateLanguageRequest $request): RedirectResponse { - $request->validate([ - 'locale' => ['required', 'string', 'in:'.implode(',', array_keys(config('languages.available')))], - ]); + $request->user()->update(['locale' => $request->validated('locale')]); - return back()->withCookie( - cookie()->forever('locale', $request->locale, '/', config('session.domain')) - ); + if (PostHogService::shouldTrack()) { + SyncUser::dispatch((string) $request->user()->id); + } + + return back(); } public function destroy(ProfileDeleteRequest $request): RedirectResponse diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php index 3d0598c3f..b586dd687 100644 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -6,7 +6,9 @@ use App\Http\Controllers\Controller; use App\Http\Requests\App\Auth\LoginRequest; +use App\Jobs\PostHog\SyncUser; use App\Models\Invite; +use App\Services\PostHogService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -36,6 +38,15 @@ public function store(LoginRequest $request): RedirectResponse $request->session()->regenerate(); + if ($locale = $request->validated('locale')) { + $user = $request->user(); + $user->update(['locale' => $locale]); + + if (PostHogService::shouldTrack()) { + SyncUser::dispatch((string) $user->id); + } + } + if ($invite = Invite::fromId($request->string('invite')->toString())) { return redirect()->route('app.invites.show', $invite); } diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 0a0a46b6a..2368af87b 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -40,6 +40,7 @@ public function store(RegisterRequest $request): RedirectResponse 'password' => $request->validated('password'), 'is_invite' => $invite !== null, 'registration_ip' => $request->ip(), + 'locale' => $request->validated('locale'), ], $attributionParameters); event(new Registered($user)); diff --git a/app/Http/Middleware/App/HandleInertiaRequests.php b/app/Http/Middleware/App/HandleInertiaRequests.php index 1978e0a4e..bd4864ba9 100644 --- a/app/Http/Middleware/App/HandleInertiaRequests.php +++ b/app/Http/Middleware/App/HandleInertiaRequests.php @@ -7,6 +7,7 @@ use App\Actions\Onboarding\ResolveOnboardingStatus; use App\Enums\Auth\SocialAuthProvider; use App\Enums\PostPlatform\ContentType; +use App\Enums\User\Locale; use App\Http\Resources\App\HandleInertiaRequests\AuthAccountResource; use App\Http\Resources\App\HandleInertiaRequests\AuthPlanResource; use App\Http\Resources\App\HandleInertiaRequests\AuthUserResource; @@ -63,10 +64,7 @@ public function share(Request $request): array 'applicationUrl' => config('app.url'), 'env' => config('app.env'), 'locale' => app()->getLocale(), - 'languages' => collect(config('languages.available'))->map(fn ($name, $code) => [ - 'code' => $code, - 'name' => $name, - ])->values()->all(), + 'languages' => Locale::options(), 'aiEnabled' => filled(config('ai.providers.'.config('ai.default').'.key')), 'selfHosted' => $isSelfHosted, 'allowMultipleSocialAccounts' => (bool) config('trypost.allow_multiple_social_accounts'), diff --git a/app/Http/Middleware/App/SetLocale.php b/app/Http/Middleware/App/SetLocale.php index 40afd5b9f..30d9812af 100644 --- a/app/Http/Middleware/App/SetLocale.php +++ b/app/Http/Middleware/App/SetLocale.php @@ -4,10 +4,11 @@ namespace App\Http\Middleware\App; -use App\Enums\Workspace\ContentLanguage; +use App\Enums\User\Locale; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\View; use Symfony\Component\HttpFoundation\Response; @@ -18,25 +19,11 @@ class SetLocale */ public function handle(Request $request, Closure $next): Response { - $available = config('languages.available'); - $locale = $request->cookie('locale'); - $isValid = $locale && array_key_exists($locale, $available); - $activeLocale = $isValid ? $locale : config('languages.default'); - $language = ContentLanguage::tryFrom($activeLocale) ?? ContentLanguage::DEFAULT; + $locale = Auth::user()?->locale ?? Locale::DEFAULT; - App::setLocale($activeLocale); - View::share('htmlDir', $language->direction()); + App::setLocale($locale->value); + View::share('htmlDir', $locale->direction()); - $response = $next($request); - - // Passport OAuth errors return a raw Symfony Response (no withCookie()). - // Attach via headers so both Illuminate and Symfony responses work. - if (! $isValid) { - $response->headers->setCookie( - cookie()->forever('locale', config('languages.default'), '/', config('session.domain')), - ); - } - - return $response; + return $next($request); } } diff --git a/app/Http/Requests/App/Auth/LoginRequest.php b/app/Http/Requests/App/Auth/LoginRequest.php index e240dfc85..350c6f193 100644 --- a/app/Http/Requests/App/Auth/LoginRequest.php +++ b/app/Http/Requests/App/Auth/LoginRequest.php @@ -4,12 +4,14 @@ namespace App\Http\Requests\App\Auth; +use App\Enums\User\Locale; use Illuminate\Auth\Events\Lockout; use Illuminate\Contracts\Validation\Rule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; +use Illuminate\Validation\Rule as ValidationRule; use Illuminate\Validation\ValidationException; class LoginRequest extends FormRequest @@ -32,6 +34,7 @@ public function rules(): array return [ 'email' => ['required', 'string', 'email'], 'password' => ['required', 'string'], + 'locale' => ['nullable', ValidationRule::enum(Locale::class)], ]; } diff --git a/app/Http/Requests/App/Auth/RegisterRequest.php b/app/Http/Requests/App/Auth/RegisterRequest.php index 13b24f6e5..3ef71615a 100644 --- a/app/Http/Requests/App/Auth/RegisterRequest.php +++ b/app/Http/Requests/App/Auth/RegisterRequest.php @@ -4,10 +4,12 @@ namespace App\Http\Requests\App\Auth; +use App\Enums\User\Locale; use App\Models\Invite; use App\Models\User; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; use Illuminate\Validation\Rules; use Illuminate\Validation\Validator; @@ -32,6 +34,7 @@ public function rules(): array 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], 'password' => ['required', Rules\Password::defaults()], + 'locale' => ['required', Rule::enum(Locale::class)], ]; } diff --git a/app/Http/Requests/App/Settings/UpdateLanguageRequest.php b/app/Http/Requests/App/Settings/UpdateLanguageRequest.php new file mode 100644 index 000000000..1589a40ae --- /dev/null +++ b/app/Http/Requests/App/Settings/UpdateLanguageRequest.php @@ -0,0 +1,23 @@ +|string> + */ + public function rules(): array + { + return [ + 'locale' => ['required', Rule::enum(Locale::class)], + ]; + } +} diff --git a/app/Http/Requests/App/Settings/UploadPhotoRequest.php b/app/Http/Requests/App/Settings/UploadPhotoRequest.php new file mode 100644 index 000000000..e2b18b23a --- /dev/null +++ b/app/Http/Requests/App/Settings/UploadPhotoRequest.php @@ -0,0 +1,21 @@ +|string> + */ + public function rules(): array + { + return [ + 'photo' => ['required', 'image', 'max:2048'], + ]; + } +} diff --git a/app/Jobs/DispatchWebhook.php b/app/Jobs/DispatchWebhook.php index d41986832..75d1934e5 100644 --- a/app/Jobs/DispatchWebhook.php +++ b/app/Jobs/DispatchWebhook.php @@ -171,7 +171,7 @@ public function failed(?Throwable $exception): void $owner = $this->webhook->workspace?->account?->owner; if ($owner?->email) { - Mail::to($owner->email)->send(new WebhookPausedMail($this->webhook)); + Mail::to($owner)->send(new WebhookPausedMail($this->webhook)); } } } diff --git a/app/Jobs/PostHog/SyncUser.php b/app/Jobs/PostHog/SyncUser.php index d2a880c52..a8f71485b 100644 --- a/app/Jobs/PostHog/SyncUser.php +++ b/app/Jobs/PostHog/SyncUser.php @@ -46,6 +46,7 @@ public function handle(PostHogService $postHog): void $postHog->identify($user->id, [ '$email' => $user->email, '$name' => $user->name, + 'locale' => $user->locale->value, '$set_once' => [ 'signed_up_at' => $user->created_at?->toIso8601String(), ...$attribution, diff --git a/app/Mail/AccountDisconnected.php b/app/Mail/AccountDisconnected.php index e6345ac7c..6a5f5e50a 100644 --- a/app/Mail/AccountDisconnected.php +++ b/app/Mail/AccountDisconnected.php @@ -26,7 +26,10 @@ public function envelope(): Envelope $workspaceName = $this->account->workspace->name; return new Envelope( - subject: "Your {$platformName} account in {$workspaceName} needs to be reconnected", + subject: __('mail.account_disconnected.subject', [ + 'platform' => $platformName, + 'workspace' => $workspaceName, + ]), ); } @@ -39,8 +42,11 @@ public function content(): Content return new Content( view: 'mail.account-disconnected', with: [ - 'title' => "Your {$platformName} account needs to be reconnected", - 'previewText' => "Please reconnect your {$platformName} account in {$workspaceName} to continue scheduling posts.", + 'title' => __('mail.account_disconnected.title', ['platform' => $platformName]), + 'previewText' => __('mail.account_disconnected.preview', [ + 'platform' => $platformName, + 'workspace' => $workspaceName, + ]), 'account' => $this->account, 'platformName' => $platformName, 'accountName' => $accountName, diff --git a/app/Mail/MentionedInComment.php b/app/Mail/MentionedInComment.php index fba430a91..2cbebd5bf 100644 --- a/app/Mail/MentionedInComment.php +++ b/app/Mail/MentionedInComment.php @@ -27,7 +27,7 @@ public function __construct( public function envelope(): Envelope { return new Envelope( - subject: __('mail.mentioned.subject', ['name' => $this->author->name]), + subject: __('mail.mentioned_in_comment.subject', ['name' => $this->author->name]), ); } @@ -36,7 +36,7 @@ public function content(): Content return new Content( view: 'mail.mentioned-in-comment', with: [ - 'title' => __('mail.mentioned.title', ['name' => $this->author->name]), + 'title' => __('mail.mentioned_in_comment.title', ['name' => $this->author->name]), 'previewText' => Str::limit($this->excerpt, 100), 'authorName' => $this->author->name, 'excerpt' => $this->excerpt, diff --git a/app/Mail/PostAtRisk.php b/app/Mail/PostAtRisk.php index 174790204..91bbc36e6 100644 --- a/app/Mail/PostAtRisk.php +++ b/app/Mail/PostAtRisk.php @@ -52,7 +52,10 @@ public function __construct( public function envelope(): Envelope { return new Envelope( - subject: $this->subjectFor($this->count), + subject: trans_choice('mail.post_at_risk.subject', $this->count, [ + 'count' => $this->count, + 'workspace' => $this->workspace->name, + ]), ); } @@ -61,12 +64,12 @@ public function content(): Content return new Content( view: 'mail.post-at-risk', with: [ - 'title' => 'Posts May Fail to Publish', - 'previewText' => $this->subjectFor($this->count), - 'intro' => "The following social accounts in your {$this->workspace->name} workspace need to be reconnected before these scheduled posts can publish:", - 'reconnectCta' => 'Please reconnect these accounts now to avoid missing your scheduled posts.', - 'buttonText' => 'Reconnect Accounts', - 'workspace' => $this->workspace, + 'title' => __('mail.post_at_risk.title'), + 'previewText' => trans_choice('mail.post_at_risk.subject', $this->count, [ + 'count' => $this->count, + 'workspace' => $this->workspace->name, + ]), + 'workspaceName' => $this->workspace->name, 'atRiskGroups' => $this->atRiskGroups(), 'url' => route('app.accounts'), ], @@ -74,7 +77,7 @@ public function content(): Content } /** - * @return Collection, postsLabel: string}> + * @return Collection, postCount: int, times: string}> */ private function atRiskGroups(): Collection { @@ -93,27 +96,17 @@ private function atRiskGroups(): Collection // handle), so it's dropped rather than crashing the render. ->filter(fn (Collection $group) => $group->first()->socialAccount !== null) ->map(function (Collection $group) { - $postCount = $group->count(); - $times = $group->sortBy(fn ($pp) => $pp->post->scheduled_at) - ->map(fn ($pp) => $pp->post->scheduled_at->format('H:i')) - ->implode(', '); - $noun = $postCount === 1 ? 'post' : 'posts'; - return [ 'account' => $group->first()->socialAccount, 'postPlatforms' => $group, - 'postsLabel' => "{$postCount} {$noun} scheduled: {$times} UTC", + 'postCount' => $group->count(), + 'times' => $group->sortBy(fn ($pp) => $pp->post->scheduled_at) + ->map(fn ($pp) => $pp->post->scheduled_at->format('H:i')) + ->implode(', '), ]; })->values(); } - private function subjectFor(int $count): string - { - $noun = $count === 1 ? 'post is' : 'posts are'; - - return "{$count} {$noun} at risk in {$this->workspace->name}"; - } - public function attachments(): array { return []; diff --git a/app/Mail/PostPublishFailed.php b/app/Mail/PostPublishFailed.php index 405d32ed9..8679681f7 100644 --- a/app/Mail/PostPublishFailed.php +++ b/app/Mail/PostPublishFailed.php @@ -24,7 +24,7 @@ public function __construct( public function envelope(): Envelope { return new Envelope( - subject: "Your post failed to publish in {$this->post->workspace->name}", + subject: __('mail.post_publish_failed.subject', ['workspace' => $this->post->workspace->name]), ); } @@ -45,9 +45,9 @@ public function content(): Content return new Content( view: 'mail.post-publish-failed', with: [ - 'title' => 'Your post failed to publish', - 'previewText' => 'One or more platforms failed to publish your post.', - 'body' => "Your scheduled post in the {$this->post->workspace->name} workspace failed to publish on one or more platforms.", + 'title' => __('mail.post_publish_failed.title'), + 'previewText' => __('mail.post_publish_failed.preview'), + 'workspaceName' => $this->post->workspace->name, 'failedPlatforms' => $failedPlatforms, 'url' => route('app.posts.edit', $this->post), ], diff --git a/app/Mail/PostPublished.php b/app/Mail/PostPublished.php index 8a51696e4..64422f001 100644 --- a/app/Mail/PostPublished.php +++ b/app/Mail/PostPublished.php @@ -24,7 +24,7 @@ public function __construct( public function envelope(): Envelope { return new Envelope( - subject: "Your post was published in {$this->post->workspace->name}", + subject: __('mail.post_published.subject', ['workspace' => $this->post->workspace->name]), ); } @@ -45,9 +45,9 @@ public function content(): Content return new Content( view: 'mail.post-published', with: [ - 'title' => 'Your post was published', - 'previewText' => 'Your post has been published successfully.', - 'body' => "Your post in the {$this->post->workspace->name} workspace has been published successfully.", + 'title' => __('mail.post_published.title'), + 'previewText' => __('mail.post_published.preview'), + 'workspaceName' => $this->post->workspace->name, 'publishedPlatforms' => $publishedPlatforms, 'url' => route('app.posts.edit', $this->post), ], diff --git a/app/Mail/WebhookPausedMail.php b/app/Mail/WebhookPausedMail.php index 4c803787e..2719184af 100644 --- a/app/Mail/WebhookPausedMail.php +++ b/app/Mail/WebhookPausedMail.php @@ -21,7 +21,7 @@ public function __construct(public Webhook $webhook) {} public function envelope(): Envelope { return new Envelope( - subject: __('webhooks.mail.paused_subject', ['endpoint' => $this->webhook->endpoint]), + subject: __('mail.webhook_paused.subject', ['endpoint' => $this->webhook->endpoint]), ); } @@ -30,10 +30,9 @@ public function content(): Content return new Content( view: 'mail.webhook-paused', with: [ - 'title' => __('webhooks.mail.paused_title'), - 'previewText' => __('webhooks.mail.paused_preview'), - 'body' => __('webhooks.mail.paused_body', ['endpoint' => $this->webhook->endpoint]), - 'buttonText' => __('webhooks.mail.paused_cta'), + 'title' => __('mail.webhook_paused.title'), + 'previewText' => __('mail.webhook_paused.preview'), + 'endpoint' => $this->webhook->endpoint, 'url' => route('app.webhooks.show', $this->webhook), ], ); diff --git a/app/Mail/WorkspaceConnectionsDisconnected.php b/app/Mail/WorkspaceConnectionsDisconnected.php index ebc889858..6b422339c 100644 --- a/app/Mail/WorkspaceConnectionsDisconnected.php +++ b/app/Mail/WorkspaceConnectionsDisconnected.php @@ -24,9 +24,7 @@ class WorkspaceConnectionsDisconnected extends Mailable implements ShouldQueue public function __construct( public Workspace $workspace, public Collection $disconnectedAccounts - ) { - $this->locale = config('app.locale'); - } + ) {} public function envelope(): Envelope { @@ -36,7 +34,7 @@ public function envelope(): Envelope subject: trans_choice('mail.workspace_connections_disconnected.subject', $count, [ 'count' => $count, 'workspace' => $this->workspace->name, - ], $this->locale), + ]), ); } @@ -44,24 +42,16 @@ public function content(): Content { $count = $this->disconnectedAccounts->count(); $workspaceName = $this->workspace->name; - $locale = $this->locale; return new Content( view: 'mail.workspace-connections-disconnected', with: [ - 'title' => __('mail.workspace_connections_disconnected.title', [], $locale), + 'title' => __('mail.workspace_connections_disconnected.title'), 'previewText' => trans_choice('mail.workspace_connections_disconnected.subject', $count, [ 'count' => $count, 'workspace' => $workspaceName, - ], $locale), - 'intro' => __('mail.workspace_connections_disconnected.intro', ['workspace' => $workspaceName], $locale), - 'reasonsTitle' => __('mail.workspace_connections_disconnected.reasons_title', [], $locale), - 'reasonExpired' => __('mail.workspace_connections_disconnected.reason_expired', [], $locale), - 'reasonRevoked' => __('mail.workspace_connections_disconnected.reason_revoked', [], $locale), - 'reasonChanged' => __('mail.workspace_connections_disconnected.reason_changed', [], $locale), - 'reconnectCta' => __('mail.workspace_connections_disconnected.reconnect_cta', [], $locale), - 'buttonText' => __('mail.workspace_connections_disconnected.button', [], $locale), - 'workspace' => $this->workspace, + ]), + 'workspaceName' => $workspaceName, 'disconnectedAccounts' => $this->disconnectedAccounts, 'url' => route('app.accounts'), ], diff --git a/app/Mail/WorkspaceInvite.php b/app/Mail/WorkspaceInvite.php index 02631c192..d136b03a3 100644 --- a/app/Mail/WorkspaceInvite.php +++ b/app/Mail/WorkspaceInvite.php @@ -23,7 +23,7 @@ public function __construct( public function envelope(): Envelope { return new Envelope( - subject: "You've been invited to join {$this->invite->account->name}", + subject: __('mail.workspace_invite.subject', ['account' => $this->invite->account->name]), ); } @@ -35,8 +35,8 @@ public function content(): Content return new Content( view: 'mail.workspace-invite', with: [ - 'title' => "You've been invited to join {$accountName}", - 'previewText' => "You've been invited to join {$accountName}", + 'title' => __('mail.workspace_invite.title', ['account' => $accountName]), + 'previewText' => __('mail.workspace_invite.preview', ['account' => $accountName]), 'accountName' => $accountName, 'roleLabel' => $roleLabel, 'url' => route('app.invites.show', $this->invite), diff --git a/app/Models/User.php b/app/Models/User.php index 7557cb44c..80b0446c4 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,6 +6,7 @@ use App\Enums\Auth\SocialAuthProvider; use App\Enums\Notification\Type as NotificationType; +use App\Enums\User\Locale; use App\Enums\User\Persona; use App\Enums\User\ReferralSource; use App\Models\Traits\HasAccount; @@ -13,6 +14,7 @@ use App\Models\Traits\HasWorkspace; use Database\Factories\UserFactory; use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -23,7 +25,7 @@ use Laravel\Passport\Contracts\OAuthenticatable; use Laravel\Passport\HasApiTokens; -class User extends Authenticatable implements MustVerifyEmail, OAuthenticatable +class User extends Authenticatable implements HasLocalePreference, MustVerifyEmail, OAuthenticatable { /** @use HasFactory */ use HasAccount, HasApiTokens, HasFactory, HasMedia, HasUuids, HasWorkspace, Notifiable; @@ -55,6 +57,7 @@ class User extends Authenticatable implements MustVerifyEmail, OAuthenticatable 'persona', 'goals', 'referral_source', + 'locale', ]; /** @@ -99,9 +102,15 @@ protected function casts(): array 'persona' => Persona::class, 'goals' => 'array', 'referral_source' => ReferralSource::class, + 'locale' => Locale::class, ]; } + public function preferredLocale(): string + { + return $this->locale->value; + } + public function notifications(): HasMany { return $this->hasMany(Notification::class); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9114a0cee..f4071b958 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -237,10 +237,10 @@ protected function configureDefaults(): void VerifyEmail::toMailUsing(function (User $user, string $url) { return (new MailMessage) ->from(config('mail.from.address'), config('mail.from.name')) - ->subject('Verify your email address') + ->subject(__('mail.email_verification.subject')) ->view('mail.email-verification', [ - 'title' => 'Verify your email address', - 'previewText' => 'Please verify your email address.', + 'title' => __('mail.email_verification.subject'), + 'previewText' => __('mail.email_verification.preview'), 'user' => $user, 'url' => $url, ]); @@ -255,10 +255,10 @@ protected function configureDefaults(): void return (new MailMessage) ->from(config('mail.from.address'), config('mail.from.name')) - ->subject('Reset your password') + ->subject(__('mail.password_reset.subject')) ->view('mail.password-reset', [ - 'title' => 'Reset your password', - 'previewText' => 'Reset your password.', + 'title' => __('mail.password_reset.subject'), + 'previewText' => __('mail.password_reset.preview'), 'user' => $user, 'url' => $url, ]); diff --git a/bootstrap/app.php b/bootstrap/app.php index 7ef532204..5bdd356a7 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -26,7 +26,7 @@ ->withMiddleware(function (Middleware $middleware): void { $middleware->trustProxies(at: '*'); - $middleware->encryptCookies(except: ['sidebar_state', 'locale']); + $middleware->encryptCookies(except: ['sidebar_state']); $middleware->web(append: [ SetLocale::class, diff --git a/config/languages.php b/config/languages.php deleted file mode 100644 index c019ef69d..000000000 --- a/config/languages.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'en' => 'English', - 'uk' => 'Українська', - 'es' => 'Español', - 'pt-BR' => 'Português', - 'fr' => 'Français', - 'de' => 'Deutsch', - 'it' => 'Italiano', - 'nl' => 'Nederlands', - 'pl' => 'Polski', - 'el' => 'Ελληνικά', - 'ja' => '日本語', - 'ko' => '한국어', - 'zh' => '中文', - 'ru' => 'Русский', - 'tr' => 'Türkçe', - 'ar' => 'العربية', - ], - - /* - |-------------------------------------------------------------------------- - | Default Language - |-------------------------------------------------------------------------- - | - | The default locale code assigned to new users. - | - */ - - 'default' => 'en', - -]; diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 8caa11623..87f61894d 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories; +use App\Enums\User\Locale; use App\Models\Account; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -47,6 +48,7 @@ public function definition(): array 'utm_content' => null, 'registration_ip' => null, 'persona' => null, + 'locale' => Locale::DEFAULT, ]; } diff --git a/database/migrations/2026_09_07_211907_add_locale_to_users_table.php b/database/migrations/2026_09_07_211907_add_locale_to_users_table.php new file mode 100644 index 000000000..b0968d425 --- /dev/null +++ b/database/migrations/2026_09_07_211907_add_locale_to_users_table.php @@ -0,0 +1,25 @@ +string('locale', 10)->default(Locale::DEFAULT->value)->after('current_workspace_id'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('locale'); + }); + } +}; diff --git a/lang/ar/mail.php b/lang/ar/mail.php index 4a0a0b20e..b46b3cd21 100644 --- a/lang/ar/mail.php +++ b/lang/ar/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'أداة مفتوحة المصدر لجدولة منشورات التواصل الاجتماعي', + 'manage_notifications' => 'إدارة الإشعارات', + 'signoff' => 'مع التحية،', + 'team' => 'فريق TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'حساب :platform في :workspace يحتاج إلى إعادة ربط', + 'title' => 'حساب :platform يحتاج إلى إعادة ربط', + 'preview' => 'أعد ربط حساب :platform في :workspace لمتابعة جدولة المنشورات.', + 'heading' => 'تم فصل الحساب', + 'intro' => 'تم فصل حساب :platform الخاص بك :account عن مساحة العمل :workspace.', + 'reasons_title' => 'قد يكون هذا حدث للأسباب التالية:', + 'reason_expired' => 'انتهت صلاحية رمز الوصول', + 'reason_revoked' => 'قمت بإلغاء وصول TryPost', + 'reason_error' => 'حدث خطأ في المصادقة', + 'reconnect_cta' => 'أعد ربط حسابك لمتابعة الجدولة والنشر.', + 'button' => 'إعادة ربط الحساب', + ], + + 'email_verification' => [ + 'subject' => 'أكّد بريدك الإلكتروني', + 'preview' => 'يرجى تأكيد بريدك الإلكتروني.', + 'greeting' => 'مرحبًا :name،', + 'body' => 'أكّد بريدك الإلكتروني بالضغط على الزر أدناه:', + 'button' => 'تأكيد البريد الإلكتروني', + 'ignore' => 'إذا لم تنشئ حسابًا، يمكنك تجاهل هذه الرسالة.', + ], + + 'mentioned_in_comment' => [ 'subject' => 'أشار إليك :name على TryPost', 'title' => 'أشار إليك :name', 'intro' => 'أشار إليك :name في تعليق على منشور.', - 'cta' => 'عرض التعليق', + 'button' => 'عرض التعليق', + ], + + 'password_reset' => [ + 'subject' => 'أعد تعيين كلمة المرور', + 'preview' => 'أعد تعيين كلمة المرور.', + 'greeting' => 'مرحبًا :name،', + 'body' => 'تلقينا طلبًا لإعادة تعيين كلمة المرور. اضغط على الزر لإنشاء كلمة مرور جديدة:', + 'button' => 'إعادة تعيين كلمة المرور', + 'expiry' => 'تنتهي صلاحية هذا الرابط خلال 60 دقيقة. إذا لم تطلب ذلك، يمكنك تجاهل هذه الرسالة.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} منشور واحد معرّض للخطر في :workspace|[0,*] :count منشورات معرّضة للخطر في :workspace', + 'title' => 'قد يفشل نشر المنشورات', + 'heading' => 'قد يفشل نشر المنشورات', + 'intro' => 'يجب إعادة ربط الحسابات التالية في مساحة العمل :workspace قبل أن يمكن نشر هذه المنشورات المجدولة:', + 'posts_label' => '{1} منشور واحد مجدول: :times UTC|[0,*] :count منشورات مجدولة: :times UTC', + 'reconnect_cta' => 'أعد ربط هذه الحسابات الآن حتى لا تفوتك منشوراتك المجدولة.', + 'button' => 'إعادة ربط الحسابات', + ], + + 'post_publish_failed' => [ + 'subject' => 'فشل نشر منشورك في :workspace', + 'title' => 'فشل نشر منشورك', + 'preview' => 'فشلت منصة واحدة أو أكثر في النشر.', + 'heading' => 'فشل نشر منشورك', + 'body' => 'فشل نشر منشورك المجدول في مساحة العمل :workspace على منصة واحدة أو أكثر.', + 'platforms_title' => 'المنصات التي فشلت:', + 'button' => 'عرض المنشور', + ], + + 'post_published' => [ + 'subject' => 'تم نشر منشورك في :workspace', + 'title' => 'تم نشر منشورك', + 'preview' => 'تم نشر منشورك بنجاح.', + 'heading' => 'تم نشر منشورك', + 'body' => 'تم نشر منشورك في مساحة العمل :workspace بنجاح.', + 'platforms_title' => 'تم النشر على:', + 'view_post' => 'عرض المنشور', + 'button' => 'عرض المنشور', + ], + + 'webhook_paused' => [ + 'subject' => 'تم إيقاف الويب هوك مؤقتًا: :endpoint', + 'title' => 'أُوقف الويب هوك مؤقتًا بعد إخفاقات متكررة', + 'preview' => 'أوقفنا ويب هوكًا مؤقتًا بعد 5 إخفاقات تسليم متتالية.', + 'heading' => 'أُوقف الويب هوك مؤقتًا بعد إخفاقات متكررة', + 'body' => 'أوقفنا الويب هوك على :endpoint مؤقتًا بعد 5 إخفاقات تسليم متتالية. راجع الـ endpoint وأعد تفعيله من صفحة تفاصيل الويب هوك.', + 'button' => 'عرض الويب هوك', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} حساب واحد يحتاج إلى إعادة الربط في :workspace|{2} حسابان يحتاجان إلى إعادة الربط في :workspace|[3,10] :count حسابات تحتاج إلى إعادة الربط في :workspace|[11,*] :count حسابًا يحتاج إلى إعادة الربط في :workspace', + 'subject' => '{1} حساب واحد يحتاج إلى إعادة الربط في :workspace|{2} حسابان يحتاجان إلى إعادة الربط في :workspace|[3,10] :count حسابات تحتاج إلى إعادة الربط في :workspace|[0,*] :count حسابًا يحتاج إلى إعادة الربط في :workspace', 'title' => 'حسابات تحتاج إلى إعادة الربط', + 'heading' => 'حسابات تحتاج إلى إعادة الربط', 'intro' => 'تم فصل الحسابات الاجتماعية التالية في مساحة العمل :workspace وتحتاج إلى إعادة الربط:', 'reasons_title' => 'قد يكون هذا قد حدث بسبب:', 'reason_expired' => 'انتهاء صلاحية رموز الوصول', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'يرجى إعادة ربط هذه الحسابات لمواصلة جدولة المنشورات ونشرها.', 'button' => 'إعادة ربط الحسابات', ], + + 'workspace_invite' => [ + 'subject' => 'تمت دعوتك للانضمام إلى :account', + 'title' => 'تمت دعوتك للانضمام إلى :account', + 'preview' => 'تمت دعوتك للانضمام إلى :account', + 'heading' => 'تمت دعوتك!', + 'intro' => 'تمت دعوتك للتعاون في مساحة العمل :account.', + 'role' => 'تمت دعوتك بصفة :role.', + 'button' => 'قبول الدعوة', + 'expiry' => 'تنتهي صلاحية هذه الدعوة خلال 7 أيام.', + ], + ]; diff --git a/lang/ar/webhooks.php b/lang/ar/webhooks.php index 51a5469b9..f3175c16b 100644 --- a/lang/ar/webhooks.php +++ b/lang/ar/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'أُعيد إرسال حدث الويب هوك.', 'tested' => 'أُرسل الحدث التجريبي.', ], - 'mail' => [ - 'paused_subject' => 'تم إيقاف الويب هوك مؤقتًا: :endpoint', - 'paused_title' => 'أُوقف الويب هوك مؤقتًا بعد إخفاقات متكررة', - 'paused_preview' => 'أوقفنا ويب هوكًا مؤقتًا بعد 5 إخفاقات تسليم متتالية.', - 'paused_body' => 'أوقفنا الويب هوك على :endpoint مؤقتًا بعد 5 إخفاقات تسليم متتالية. راجع الـ endpoint وأعد تفعيله من صفحة تفاصيل الويب هوك.', - 'paused_cta' => 'عرض الويب هوك', - ], ]; diff --git a/lang/de/mail.php b/lang/de/mail.php index 4a44b52a0..4b0c1401d 100644 --- a/lang/de/mail.php +++ b/lang/de/mail.php @@ -3,16 +3,97 @@ declare(strict_types=1); return [ - 'mentioned' => [ + + 'layout' => [ + 'tagline' => 'Open-Source-Tool zur Planung von Social-Media-Beiträgen', + 'manage_notifications' => 'Benachrichtigungen verwalten', + 'signoff' => 'Viele Grüße,', + 'team' => 'Das TryPost-Team', + ], + + 'account_disconnected' => [ + 'subject' => 'Dein :platform-Konto in :workspace muss neu verbunden werden', + 'title' => 'Dein :platform-Konto muss neu verbunden werden', + 'preview' => 'Verbinde dein :platform-Konto in :workspace neu, um weiter Beiträge zu planen.', + 'heading' => 'Konto getrennt', + 'intro' => 'Dein :platform-Konto :account wurde vom Workspace :workspace getrennt.', + 'reasons_title' => 'Mögliche Gründe:', + 'reason_expired' => 'Dein Zugriffstoken ist abgelaufen', + 'reason_revoked' => 'Du hast den Zugriff von TryPost widerrufen', + 'reason_error' => 'Es gab einen Authentifizierungsfehler', + 'reconnect_cta' => 'Verbinde dein Konto neu, um weiter zu planen und zu veröffentlichen.', + 'button' => 'Konto neu verbinden', + ], + + 'email_verification' => [ + 'subject' => 'Bestätige deine E-Mail-Adresse', + 'preview' => 'Bitte bestätige deine E-Mail-Adresse.', + 'greeting' => 'Hallo :name,', + 'body' => 'Bestätige deine E-Mail-Adresse über den Button unten:', + 'button' => 'E-Mail bestätigen', + 'ignore' => 'Wenn du kein Konto erstellt hast, kannst du diese E-Mail ignorieren.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name hat dich auf TryPost erwähnt', 'title' => ':name hat dich erwähnt', 'intro' => ':name hat dich in einem Beitragskommentar erwähnt.', - 'cta' => 'Kommentar ansehen', + 'button' => 'Kommentar ansehen', + ], + + 'password_reset' => [ + 'subject' => 'Setze dein Passwort zurück', + 'preview' => 'Setze dein Passwort zurück.', + 'greeting' => 'Hallo :name,', + 'body' => 'Wir haben eine Anfrage zum Zurücksetzen deines Passworts erhalten. Klicke auf den Button, um ein neues zu erstellen:', + 'button' => 'Passwort zurücksetzen', + 'expiry' => 'Dieser Link läuft in 60 Minuten ab. Wenn du die Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count Beitrag in :workspace ist gefährdet|[0,*] :count Beiträge in :workspace sind gefährdet', + 'title' => 'Beiträge könnten fehlschlagen', + 'heading' => 'Beiträge könnten fehlschlagen', + 'intro' => 'Die folgenden Konten im Workspace :workspace müssen neu verbunden werden, damit diese geplanten Beiträge veröffentlicht werden können:', + 'posts_label' => '{1} :count Beitrag geplant: :times UTC|[0,*] :count Beiträge geplant: :times UTC', + 'reconnect_cta' => 'Verbinde diese Konten jetzt neu, damit deine geplanten Beiträge nicht ausfallen.', + 'button' => 'Konten neu verbinden', + ], + + 'post_publish_failed' => [ + 'subject' => 'Dein Beitrag in :workspace konnte nicht veröffentlicht werden', + 'title' => 'Dein Beitrag konnte nicht veröffentlicht werden', + 'preview' => 'Eine oder mehrere Plattformen konnten nicht veröffentlichen.', + 'heading' => 'Dein Beitrag konnte nicht veröffentlicht werden', + 'body' => 'Dein geplanter Beitrag im Workspace :workspace konnte auf einer oder mehreren Plattformen nicht veröffentlicht werden.', + 'platforms_title' => 'Fehlgeschlagene Plattformen:', + 'button' => 'Beitrag ansehen', + ], + + 'post_published' => [ + 'subject' => 'Dein Beitrag in :workspace wurde veröffentlicht', + 'title' => 'Dein Beitrag wurde veröffentlicht', + 'preview' => 'Dein Beitrag wurde erfolgreich veröffentlicht.', + 'heading' => 'Dein Beitrag wurde veröffentlicht', + 'body' => 'Dein Beitrag im Workspace :workspace wurde erfolgreich veröffentlicht.', + 'platforms_title' => 'Veröffentlicht auf:', + 'view_post' => 'Beitrag ansehen', + 'button' => 'Beitrag ansehen', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook pausiert: :endpoint', + 'title' => 'Webhook nach wiederholten Fehlern pausiert', + 'preview' => 'Wir haben einen Webhook nach 5 aufeinanderfolgenden Zustellfehlern pausiert.', + 'heading' => 'Webhook nach wiederholten Fehlern pausiert', + 'body' => 'Wir haben den Webhook unter :endpoint nach 5 aufeinanderfolgenden Zustellfehlern pausiert. Prüfe den Endpoint und aktiviere ihn wieder auf der Webhook-Detailseite.', + 'button' => 'Webhook anzeigen', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count Konto muss in :workspace erneut verbunden werden|[2,*] :count Konten müssen in :workspace erneut verbunden werden', + 'subject' => '{1} :count Konto muss in :workspace erneut verbunden werden|[0,*] :count Konten müssen in :workspace erneut verbunden werden', 'title' => 'Konten müssen erneut verbunden werden', + 'heading' => 'Konten müssen erneut verbunden werden', 'intro' => 'Die folgenden Social-Media-Konten in deinem Workspace :workspace wurden getrennt und müssen erneut verbunden werden:', 'reasons_title' => 'Das kann folgende Gründe haben:', 'reason_expired' => 'Zugriffstokens sind abgelaufen', @@ -21,4 +102,16 @@ 'reconnect_cta' => 'Bitte verbinde diese Konten erneut, um weiterhin Beiträge zu planen und zu veröffentlichen.', 'button' => 'Konten erneut verbinden', ], + + 'workspace_invite' => [ + 'subject' => 'Du wurdest zu :account eingeladen', + 'title' => 'Du wurdest zu :account eingeladen', + 'preview' => 'Du wurdest zu :account eingeladen', + 'heading' => 'Du wurdest eingeladen!', + 'intro' => 'Du wurdest eingeladen, im Workspace :account mitzuarbeiten.', + 'role' => 'Du wurdest als :role eingeladen.', + 'button' => 'Einladung annehmen', + 'expiry' => 'Diese Einladung läuft in 7 Tagen ab.', + ], + ]; diff --git a/lang/de/webhooks.php b/lang/de/webhooks.php index f8dead81c..abf1eb7e3 100644 --- a/lang/de/webhooks.php +++ b/lang/de/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Webhook-Ereignis erneut gesendet.', 'tested' => 'Test-Ereignis gesendet.', ], - 'mail' => [ - 'paused_subject' => 'Webhook pausiert: :endpoint', - 'paused_title' => 'Webhook nach wiederholten Fehlern pausiert', - 'paused_preview' => 'Wir haben einen Webhook nach 5 aufeinanderfolgenden Zustellfehlern pausiert.', - 'paused_body' => 'Wir haben den Webhook unter :endpoint nach 5 aufeinanderfolgenden Zustellfehlern pausiert. Prüfe den Endpoint und aktiviere ihn wieder auf der Webhook-Detailseite.', - 'paused_cta' => 'Webhook anzeigen', - ], ]; diff --git a/lang/el/mail.php b/lang/el/mail.php index 770372cae..4398c0a06 100644 --- a/lang/el/mail.php +++ b/lang/el/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Εργαλείο ανοιχτού κώδικα για προγραμματισμό δημοσιεύσεων', + 'manage_notifications' => 'Διαχείριση ειδοποιήσεων', + 'signoff' => 'Με εκτίμηση,', + 'team' => 'Η ομάδα του TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Ο λογαριασμός :platform στο :workspace χρειάζεται επανασύνδεση', + 'title' => 'Ο λογαριασμός :platform χρειάζεται επανασύνδεση', + 'preview' => 'Επανασυνδέστε τον λογαριασμό :platform στο :workspace για να συνεχίσετε να προγραμματίζετε δημοσιεύσεις.', + 'heading' => 'Ο λογαριασμός αποσυνδέθηκε', + 'intro' => 'Ο λογαριασμός σας :platform :account αποσυνδέθηκε από τον χώρο εργασίας :workspace.', + 'reasons_title' => 'Αυτό μπορεί να συνέβη επειδή:', + 'reason_expired' => 'Το διακριτικό πρόσβασης έληξε', + 'reason_revoked' => 'Ανακαλέσατε την πρόσβαση του TryPost', + 'reason_error' => 'Παρουσιάστηκε σφάλμα ταυτοποίησης', + 'reconnect_cta' => 'Επανασυνδέστε τον λογαριασμό σας για να συνεχίσετε να προγραμματίζετε και να δημοσιεύετε.', + 'button' => 'Επανασύνδεση λογαριασμού', + ], + + 'email_verification' => [ + 'subject' => 'Επιβεβαιώστε τη διεύθυνση email σας', + 'preview' => 'Επιβεβαιώστε τη διεύθυνση email σας.', + 'greeting' => 'Γεια σου :name,', + 'body' => 'Επιβεβαιώστε τη διεύθυνση email σας πατώντας το κουμπί παρακάτω:', + 'button' => 'Επιβεβαίωση email', + 'ignore' => 'Αν δεν δημιουργήσατε λογαριασμό, μπορείτε να αγνοήσετε αυτό το email.', + ], + + 'mentioned_in_comment' => [ 'subject' => 'Ο/Η :name σας ανέφερε στο TryPost', 'title' => 'Ο/Η :name σας ανέφερε', 'intro' => 'Ο/Η :name σας ανέφερε σε σχόλιο μιας δημοσίευσης.', - 'cta' => 'Προβολή σχολίου', + 'button' => 'Προβολή σχολίου', + ], + + 'password_reset' => [ + 'subject' => 'Επαναφέρετε τον κωδικό σας', + 'preview' => 'Επαναφέρετε τον κωδικό σας.', + 'greeting' => 'Γεια σου :name,', + 'body' => 'Λάβαμε αίτημα επαναφοράς του κωδικού σας. Πατήστε το κουμπί για να ορίσετε νέο:', + 'button' => 'Επαναφορά κωδικού', + 'expiry' => 'Ο σύνδεσμος λήγει σε 60 λεπτά. Αν δεν ζητήσατε επαναφορά, μπορείτε να αγνοήσετε αυτό το email.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count δημοσίευση σε κίνδυνο στο :workspace|[0,*] :count δημοσιεύσεις σε κίνδυνο στο :workspace', + 'title' => 'Οι δημοσιεύσεις μπορεί να αποτύχουν', + 'heading' => 'Οι δημοσιεύσεις μπορεί να αποτύχουν', + 'intro' => 'Οι παρακάτω λογαριασμοί στον χώρο εργασίας :workspace χρειάζονται επανασύνδεση για να δημοσιευτούν αυτές οι προγραμματισμένες δημοσιεύσεις:', + 'posts_label' => '{1} :count προγραμματισμένη δημοσίευση: :times UTC|[0,*] :count προγραμματισμένες δημοσιεύσεις: :times UTC', + 'reconnect_cta' => 'Επανασυνδέστε τώρα αυτούς τους λογαριασμούς για να μη χάσετε τις προγραμματισμένες δημοσιεύσεις σας.', + 'button' => 'Επανασύνδεση λογαριασμών', + ], + + 'post_publish_failed' => [ + 'subject' => 'Η δημοσίευσή σας απέτυχε στο :workspace', + 'title' => 'Η δημοσίευσή σας απέτυχε', + 'preview' => 'Μία ή περισσότερες πλατφόρμες δεν κατάφεραν να δημοσιεύσουν.', + 'heading' => 'Η δημοσίευσή σας απέτυχε', + 'body' => 'Η προγραμματισμένη δημοσίευσή σας στον χώρο εργασίας :workspace απέτυχε σε μία ή περισσότερες πλατφόρμες.', + 'platforms_title' => 'Πλατφόρμες με σφάλμα:', + 'button' => 'Προβολή δημοσίευσης', + ], + + 'post_published' => [ + 'subject' => 'Η δημοσίευσή σας δημοσιεύτηκε στο :workspace', + 'title' => 'Η δημοσίευσή σας δημοσιεύτηκε', + 'preview' => 'Η δημοσίευσή σας ολοκληρώθηκε με επιτυχία.', + 'heading' => 'Η δημοσίευσή σας δημοσιεύτηκε', + 'body' => 'Η δημοσίευσή σας στον χώρο εργασίας :workspace ολοκληρώθηκε με επιτυχία.', + 'platforms_title' => 'Δημοσιεύτηκε σε:', + 'view_post' => 'Προβολή δημοσίευσης', + 'button' => 'Προβολή δημοσίευσης', + ], + + 'webhook_paused' => [ + 'subject' => 'Το webhook τέθηκε σε παύση: :endpoint', + 'title' => 'Το webhook τέθηκε σε παύση μετά από επαναλαμβανόμενες αποτυχίες', + 'preview' => 'Θέσαμε ένα webhook σε παύση μετά από 5 διαδοχικές αποτυχίες παράδοσης.', + 'heading' => 'Το webhook τέθηκε σε παύση μετά από επαναλαμβανόμενες αποτυχίες', + 'body' => 'Θέσαμε το webhook στο :endpoint σε παύση μετά από 5 διαδοχικές αποτυχίες παράδοσης. Ελέγξτε το endpoint και ενεργοποιήστε το ξανά από τη σελίδα λεπτομερειών του webhook.', + 'button' => 'Προβολή webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count λογαριασμός χρειάζεται επανασύνδεση στο :workspace|[2,*] :count λογαριασμοί χρειάζονται επανασύνδεση στο :workspace', + 'subject' => '{1} :count λογαριασμός χρειάζεται επανασύνδεση στο :workspace|[0,*] :count λογαριασμοί χρειάζονται επανασύνδεση στο :workspace', 'title' => 'Λογαριασμοί που χρειάζονται επανασύνδεση', + 'heading' => 'Λογαριασμοί που χρειάζονται επανασύνδεση', 'intro' => 'Οι παρακάτω λογαριασμοί κοινωνικών δικτύων στο workspace :workspace αποσυνδέθηκαν και χρειάζονται επανασύνδεση:', 'reasons_title' => 'Αυτό μπορεί να συνέβη επειδή:', 'reason_expired' => 'Τα tokens πρόσβασης έληξαν', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Παρακαλούμε επανασυνδέστε αυτούς τους λογαριασμούς για να συνεχίσετε τον προγραμματισμό και τη δημοσίευση.', 'button' => 'Επανασύνδεση λογαριασμών', ], + + 'workspace_invite' => [ + 'subject' => 'Προσκληθήκατε να συμμετάσχετε στο :account', + 'title' => 'Προσκληθήκατε να συμμετάσχετε στο :account', + 'preview' => 'Προσκληθήκατε να συμμετάσχετε στο :account', + 'heading' => 'Έχετε πρόσκληση!', + 'intro' => 'Προσκληθήκατε να συνεργαστείτε στον χώρο εργασίας :account.', + 'role' => 'Προσκληθήκατε ως :role.', + 'button' => 'Αποδοχή πρόσκλησης', + 'expiry' => 'Αυτή η πρόσκληση λήγει σε 7 ημέρες.', + ], + ]; diff --git a/lang/el/webhooks.php b/lang/el/webhooks.php index 3df8adf85..0ed7d70fc 100644 --- a/lang/el/webhooks.php +++ b/lang/el/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Το συμβάν του webhook στάλθηκε ξανά.', 'tested' => 'Το δοκιμαστικό συμβάν στάλθηκε.', ], - 'mail' => [ - 'paused_subject' => 'Το webhook τέθηκε σε παύση: :endpoint', - 'paused_title' => 'Το webhook τέθηκε σε παύση μετά από επαναλαμβανόμενες αποτυχίες', - 'paused_preview' => 'Θέσαμε ένα webhook σε παύση μετά από 5 διαδοχικές αποτυχίες παράδοσης.', - 'paused_body' => 'Θέσαμε το webhook στο :endpoint σε παύση μετά από 5 διαδοχικές αποτυχίες παράδοσης. Ελέγξτε το endpoint και ενεργοποιήστε το ξανά από τη σελίδα λεπτομερειών του webhook.', - 'paused_cta' => 'Προβολή webhook', - ], ]; diff --git a/lang/en/mail.php b/lang/en/mail.php index f97c7ef68..dff87aa93 100644 --- a/lang/en/mail.php +++ b/lang/en/mail.php @@ -1,22 +1,117 @@ [ + + 'layout' => [ + 'tagline' => 'Open-source social media scheduling tool', + 'manage_notifications' => 'Manage notifications', + 'signoff' => 'Best regards,', + 'team' => 'The TryPost Team', + ], + + 'account_disconnected' => [ + 'subject' => 'Your :platform account in :workspace needs to be reconnected', + 'title' => 'Your :platform account needs to be reconnected', + 'preview' => 'Please reconnect your :platform account in :workspace to continue scheduling posts.', + 'heading' => 'Account Disconnected', + 'intro' => 'Your :platform account :account has been disconnected from the :workspace workspace.', + 'reasons_title' => 'This may have happened because:', + 'reason_expired' => 'Your access token expired', + 'reason_revoked' => 'You revoked access to TryPost', + 'reason_error' => 'There was an authentication error', + 'reconnect_cta' => 'Please reconnect your account to continue scheduling and publishing posts.', + 'button' => 'Reconnect Account', + ], + + 'email_verification' => [ + 'subject' => 'Verify your email address', + 'preview' => 'Please verify your email address.', + 'greeting' => 'Hi :name,', + 'body' => 'Please confirm your email address by clicking the button below:', + 'button' => 'Verify Email Address', + 'ignore' => 'If you did not create an account, you can safely ignore this email.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name mentioned you on TryPost', 'title' => ':name mentioned you', 'intro' => ':name mentioned you in a post comment.', - 'cta' => 'View comment', + 'button' => 'View comment', + ], + + 'password_reset' => [ + 'subject' => 'Reset your password', + 'preview' => 'Reset your password.', + 'greeting' => 'Hi :name,', + 'body' => 'We received a request to reset your password. Click the button below to create a new password:', + 'button' => 'Reset Password', + 'expiry' => 'This link expires in 60 minutes. If you did not request a password reset, you can safely ignore this email.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count post is at risk in :workspace|[0,*] :count posts are at risk in :workspace', + 'title' => 'Posts May Fail to Publish', + 'heading' => 'Posts May Fail to Publish', + 'intro' => 'The following social accounts in your :workspace workspace need to be reconnected before these scheduled posts can publish:', + 'posts_label' => '{1} :count post scheduled: :times UTC|[0,*] :count posts scheduled: :times UTC', + 'reconnect_cta' => 'Please reconnect these accounts now to avoid missing your scheduled posts.', + 'button' => 'Reconnect Accounts', + ], + + 'post_publish_failed' => [ + 'subject' => 'Your post failed to publish in :workspace', + 'title' => 'Your post failed to publish', + 'preview' => 'One or more platforms failed to publish your post.', + 'heading' => 'Your post failed to publish', + 'body' => 'Your scheduled post in the :workspace workspace failed to publish on one or more platforms.', + 'platforms_title' => 'Failed platforms:', + 'button' => 'View Post', + ], + + 'post_published' => [ + 'subject' => 'Your post was published in :workspace', + 'title' => 'Your post was published', + 'preview' => 'Your post has been published successfully.', + 'heading' => 'Your post was published', + 'body' => 'Your post in the :workspace workspace has been published successfully.', + 'platforms_title' => 'Published on:', + 'view_post' => 'View post', + 'button' => 'View Post', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook paused: :endpoint', + 'title' => 'Webhook paused after repeated failures', + 'preview' => 'We paused a webhook after 5 consecutive delivery failures.', + 'heading' => 'Webhook paused after repeated failures', + 'body' => 'We paused the webhook at :endpoint after 5 consecutive delivery failures. Review the endpoint and re-enable it from the webhook details page.', + 'button' => 'View webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count account needs to be reconnected in :workspace|[2,*] :count accounts need to be reconnected in :workspace', - 'title' => 'Accounts Need Reconnection', + 'subject' => '{1} :count account needs to be reconnected in :workspace|[0,*] :count accounts need to be reconnected in :workspace', + 'title' => 'Accounts need reconnection', + 'heading' => 'Accounts need reconnection', 'intro' => 'The following social accounts in your :workspace workspace have been disconnected and need to be reconnected:', 'reasons_title' => 'This may have happened because:', 'reason_expired' => 'Access tokens expired', 'reason_revoked' => 'You revoked access to TryPost on the platform', 'reason_changed' => 'The platform changed their authentication requirements', 'reconnect_cta' => 'Please reconnect these accounts to continue scheduling and publishing posts.', - 'button' => 'Reconnect Accounts', + 'button' => 'Reconnect accounts', ], + + 'workspace_invite' => [ + 'subject' => 'You\'ve been invited to join :account', + 'title' => 'You\'ve been invited to join :account', + 'preview' => 'You\'ve been invited to join :account', + 'heading' => 'You\'ve been invited!', + 'intro' => 'You\'ve been invited to collaborate on the :account workspace.', + 'role' => 'You\'ve been invited as :role.', + 'button' => 'Accept Invite', + 'expiry' => 'This invite expires in 7 days.', + ], + ]; diff --git a/lang/en/webhooks.php b/lang/en/webhooks.php index a3dce5c6c..875205436 100644 --- a/lang/en/webhooks.php +++ b/lang/en/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Webhook event replayed.', 'tested' => 'Test event sent.', ], - 'mail' => [ - 'paused_subject' => 'Webhook paused: :endpoint', - 'paused_title' => 'Webhook paused after repeated failures', - 'paused_preview' => 'We paused a webhook after 5 consecutive delivery failures.', - 'paused_body' => 'We paused the webhook at :endpoint after 5 consecutive delivery failures. Review the endpoint and re-enable it from the webhook details page.', - 'paused_cta' => 'View webhook', - ], ]; diff --git a/lang/es/mail.php b/lang/es/mail.php index de1abb305..fb11c4b0c 100644 --- a/lang/es/mail.php +++ b/lang/es/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Herramienta de código abierto para programar redes sociales', + 'manage_notifications' => 'Gestionar notificaciones', + 'signoff' => 'Un saludo,', + 'team' => 'El equipo de TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Tu cuenta de :platform en :workspace necesita reconectarse', + 'title' => 'Tu cuenta de :platform necesita reconectarse', + 'preview' => 'Reconecta tu cuenta de :platform en :workspace para seguir programando publicaciones.', + 'heading' => 'Cuenta desconectada', + 'intro' => 'Tu cuenta de :platform :account se ha desconectado del espacio de trabajo :workspace.', + 'reasons_title' => 'Esto puede haber ocurrido porque:', + 'reason_expired' => 'Tu token de acceso caducó', + 'reason_revoked' => 'Revocaste el acceso a TryPost', + 'reason_error' => 'Hubo un error de autenticación', + 'reconnect_cta' => 'Reconecta tu cuenta para seguir programando y publicando.', + 'button' => 'Reconectar cuenta', + ], + + 'email_verification' => [ + 'subject' => 'Verifica tu dirección de correo', + 'preview' => 'Verifica tu dirección de correo.', + 'greeting' => 'Hola :name:', + 'body' => 'Confirma tu dirección de correo haciendo clic en el botón de abajo:', + 'button' => 'Verificar correo', + 'ignore' => 'Si no creaste una cuenta, puedes ignorar este correo sin problema.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name te mencionó en TryPost', 'title' => ':name te mencionó', 'intro' => ':name te mencionó en un comentario.', - 'cta' => 'Ver comentario', + 'button' => 'Ver comentario', + ], + + 'password_reset' => [ + 'subject' => 'Restablece tu contraseña', + 'preview' => 'Restablece tu contraseña.', + 'greeting' => 'Hola :name:', + 'body' => 'Recibimos una solicitud para restablecer tu contraseña. Haz clic en el botón para crear una nueva:', + 'button' => 'Restablecer contraseña', + 'expiry' => 'Este enlace caduca en 60 minutos. Si no solicitaste el cambio, puedes ignorar este correo sin problema.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count publicación en riesgo en :workspace|[0,*] :count publicaciones en riesgo en :workspace', + 'title' => 'Tus publicaciones podrían fallar', + 'heading' => 'Tus publicaciones podrían fallar', + 'intro' => 'Las siguientes cuentas del espacio de trabajo :workspace necesitan reconectarse antes de que estas publicaciones programadas puedan salir:', + 'posts_label' => '{1} :count publicación programada: :times UTC|[0,*] :count publicaciones programadas: :times UTC', + 'reconnect_cta' => 'Reconecta estas cuentas ahora para no perder tus publicaciones programadas.', + 'button' => 'Reconectar cuentas', + ], + + 'post_publish_failed' => [ + 'subject' => 'Tu publicación falló en :workspace', + 'title' => 'Tu publicación falló', + 'preview' => 'Una o más plataformas no pudieron publicar.', + 'heading' => 'Tu publicación falló', + 'body' => 'Tu publicación programada en el espacio de trabajo :workspace falló en una o más plataformas.', + 'platforms_title' => 'Plataformas con error:', + 'button' => 'Ver publicación', + ], + + 'post_published' => [ + 'subject' => 'Tu publicación salió en :workspace', + 'title' => 'Tu publicación salió', + 'preview' => 'Tu publicación se publicó correctamente.', + 'heading' => 'Tu publicación salió', + 'body' => 'Tu publicación en el espacio de trabajo :workspace se publicó correctamente.', + 'platforms_title' => 'Publicada en:', + 'view_post' => 'Ver publicación', + 'button' => 'Ver publicación', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook pausado: :endpoint', + 'title' => 'Webhook pausado tras fallos repetidos', + 'preview' => 'Pausamos un webhook tras 5 fallos consecutivos de entrega.', + 'heading' => 'Webhook pausado tras fallos repetidos', + 'body' => 'Pausamos el webhook en :endpoint tras 5 fallos consecutivos de entrega. Revisa el endpoint y actívalo de nuevo en la página de detalles del webhook.', + 'button' => 'Ver webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count cuenta necesita ser reconectada en :workspace|[2,*] :count cuentas necesitan ser reconectadas en :workspace', + 'subject' => '{1} :count cuenta necesita ser reconectada en :workspace|[0,*] :count cuentas necesitan ser reconectadas en :workspace', 'title' => 'Cuentas necesitan reconexión', + 'heading' => 'Cuentas necesitan reconexión', 'intro' => 'Las siguientes cuentas sociales en tu workspace :workspace se han desconectado y necesitan ser reconectadas:', 'reasons_title' => 'Esto puede haber ocurrido porque:', 'reason_expired' => 'Los tokens de acceso expiraron', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Reconecta estas cuentas para seguir programando y publicando posts.', 'button' => 'Reconectar cuentas', ], + + 'workspace_invite' => [ + 'subject' => 'Te han invitado a unirte a :account', + 'title' => 'Te han invitado a unirte a :account', + 'preview' => 'Te han invitado a unirte a :account', + 'heading' => '¡Te han invitado!', + 'intro' => 'Te han invitado a colaborar en el espacio de trabajo :account.', + 'role' => 'Te han invitado como :role.', + 'button' => 'Aceptar invitación', + 'expiry' => 'Esta invitación caduca en 7 días.', + ], + ]; diff --git a/lang/es/webhooks.php b/lang/es/webhooks.php index dff8a47aa..38080c7a5 100644 --- a/lang/es/webhooks.php +++ b/lang/es/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Evento del webhook reenviado.', 'tested' => 'Evento de prueba enviado.', ], - 'mail' => [ - 'paused_subject' => 'Webhook pausado: :endpoint', - 'paused_title' => 'Webhook pausado tras fallos repetidos', - 'paused_preview' => 'Pausamos un webhook tras 5 fallos consecutivos de entrega.', - 'paused_body' => 'Pausamos el webhook en :endpoint tras 5 fallos consecutivos de entrega. Revisa el endpoint y actívalo de nuevo en la página de detalles del webhook.', - 'paused_cta' => 'Ver webhook', - ], ]; diff --git a/lang/fr/mail.php b/lang/fr/mail.php index bcd6a2049..5300e6531 100644 --- a/lang/fr/mail.php +++ b/lang/fr/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Outil open source de planification pour les réseaux sociaux', + 'manage_notifications' => 'Gérer les notifications', + 'signoff' => 'Cordialement,', + 'team' => 'L\'équipe TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Votre compte :platform dans :workspace doit être reconnecté', + 'title' => 'Votre compte :platform doit être reconnecté', + 'preview' => 'Reconnectez votre compte :platform dans :workspace pour continuer à planifier vos publications.', + 'heading' => 'Compte déconnecté', + 'intro' => 'Votre compte :platform :account a été déconnecté de l’espace de travail :workspace.', + 'reasons_title' => 'Cela peut être dû à :', + 'reason_expired' => 'Votre jeton d’accès a expiré', + 'reason_revoked' => 'Vous avez révoqué l’accès de TryPost', + 'reason_error' => 'Une erreur d’authentification est survenue', + 'reconnect_cta' => 'Reconnectez votre compte pour continuer à planifier et publier.', + 'button' => 'Reconnecter le compte', + ], + + 'email_verification' => [ + 'subject' => 'Vérifiez votre adresse e-mail', + 'preview' => 'Veuillez vérifier votre adresse e-mail.', + 'greeting' => 'Bonjour :name,', + 'body' => 'Confirmez votre adresse e-mail en cliquant sur le bouton ci-dessous :', + 'button' => 'Vérifier mon e-mail', + 'ignore' => 'Si vous n’avez pas créé de compte, vous pouvez ignorer cet e-mail.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name vous a mentionné sur TryPost', 'title' => ':name vous a mentionné', 'intro' => ':name vous a mentionné dans le commentaire d\'une publication.', - 'cta' => 'Voir le commentaire', + 'button' => 'Voir le commentaire', + ], + + 'password_reset' => [ + 'subject' => 'Réinitialisez votre mot de passe', + 'preview' => 'Réinitialisez votre mot de passe.', + 'greeting' => 'Bonjour :name,', + 'body' => 'Nous avons reçu une demande de réinitialisation de votre mot de passe. Cliquez sur le bouton pour en créer un nouveau :', + 'button' => 'Réinitialiser le mot de passe', + 'expiry' => 'Ce lien expire dans 60 minutes. Si vous n’êtes pas à l’origine de cette demande, vous pouvez ignorer cet e-mail.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count publication menacée dans :workspace|[0,*] :count publications menacées dans :workspace', + 'title' => 'Vos publications risquent d’échouer', + 'heading' => 'Vos publications risquent d’échouer', + 'intro' => 'Les comptes suivants de l’espace de travail :workspace doivent être reconnectés avant que ces publications planifiées puissent partir :', + 'posts_label' => '{1} :count publication planifiée : :times UTC|[0,*] :count publications planifiées : :times UTC', + 'reconnect_cta' => 'Reconnectez ces comptes dès maintenant pour ne pas manquer vos publications planifiées.', + 'button' => 'Reconnecter les comptes', + ], + + 'post_publish_failed' => [ + 'subject' => 'Votre publication a échoué dans :workspace', + 'title' => 'Votre publication a échoué', + 'preview' => 'Une ou plusieurs plateformes n’ont pas pu publier.', + 'heading' => 'Votre publication a échoué', + 'body' => 'Votre publication planifiée dans l’espace de travail :workspace a échoué sur une ou plusieurs plateformes.', + 'platforms_title' => 'Plateformes en échec :', + 'button' => 'Voir la publication', + ], + + 'post_published' => [ + 'subject' => 'Votre publication est en ligne dans :workspace', + 'title' => 'Votre publication est en ligne', + 'preview' => 'Votre publication a bien été publiée.', + 'heading' => 'Votre publication est en ligne', + 'body' => 'Votre publication dans l’espace de travail :workspace a bien été publiée.', + 'platforms_title' => 'Publiée sur :', + 'view_post' => 'Voir la publication', + 'button' => 'Voir la publication', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook en pause : :endpoint', + 'title' => 'Webhook mis en pause après des échecs répétés', + 'preview' => 'Nous avons mis un webhook en pause après 5 échecs de livraison consécutifs.', + 'heading' => 'Webhook mis en pause après des échecs répétés', + 'body' => 'Nous avons mis le webhook de :endpoint en pause après 5 échecs de livraison consécutifs. Vérifiez l\'endpoint et réactivez-le depuis la page de détails du webhook.', + 'button' => 'Voir le webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count compte doit être reconnecté dans :workspace|[2,*] :count comptes doivent être reconnectés dans :workspace', + 'subject' => '{1} :count compte doit être reconnecté dans :workspace|[0,*] :count comptes doivent être reconnectés dans :workspace', 'title' => 'Des comptes doivent être reconnectés', + 'heading' => 'Des comptes doivent être reconnectés', 'intro' => 'Les comptes sociaux suivants de votre espace de travail :workspace ont été déconnectés et doivent être reconnectés :', 'reasons_title' => 'Cela peut être dû à l\'une des raisons suivantes :', 'reason_expired' => 'Les jetons d\'accès ont expiré', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Veuillez reconnecter ces comptes pour continuer à programmer et publier vos publications.', 'button' => 'Reconnecter les comptes', ], + + 'workspace_invite' => [ + 'subject' => 'Vous êtes invité à rejoindre :account', + 'title' => 'Vous êtes invité à rejoindre :account', + 'preview' => 'Vous êtes invité à rejoindre :account', + 'heading' => 'Vous êtes invité !', + 'intro' => 'Vous êtes invité à collaborer sur l’espace de travail :account.', + 'role' => 'Vous êtes invité en tant que :role.', + 'button' => 'Accepter l’invitation', + 'expiry' => 'Cette invitation expire dans 7 jours.', + ], + ]; diff --git a/lang/fr/webhooks.php b/lang/fr/webhooks.php index 4748969ad..2b087ebaf 100644 --- a/lang/fr/webhooks.php +++ b/lang/fr/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Événement du webhook renvoyé.', 'tested' => 'Événement de test envoyé.', ], - 'mail' => [ - 'paused_subject' => 'Webhook en pause : :endpoint', - 'paused_title' => 'Webhook mis en pause après des échecs répétés', - 'paused_preview' => 'Nous avons mis un webhook en pause après 5 échecs de livraison consécutifs.', - 'paused_body' => 'Nous avons mis le webhook de :endpoint en pause après 5 échecs de livraison consécutifs. Vérifiez l\'endpoint et réactivez-le depuis la page de détails du webhook.', - 'paused_cta' => 'Voir le webhook', - ], ]; diff --git a/lang/it/mail.php b/lang/it/mail.php index 78bbc91da..5f515d1ef 100644 --- a/lang/it/mail.php +++ b/lang/it/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Strumento open source per pianificare i social', + 'manage_notifications' => 'Gestisci le notifiche', + 'signoff' => 'Cordiali saluti,', + 'team' => 'Il team di TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Il tuo account :platform in :workspace deve essere ricollegato', + 'title' => 'Il tuo account :platform deve essere ricollegato', + 'preview' => 'Ricollega il tuo account :platform in :workspace per continuare a pianificare i post.', + 'heading' => 'Account disconnesso', + 'intro' => 'Il tuo account :platform :account è stato disconnesso dallo spazio di lavoro :workspace.', + 'reasons_title' => 'Può essere successo perché:', + 'reason_expired' => 'Il tuo token di accesso è scaduto', + 'reason_revoked' => 'Hai revocato l’accesso a TryPost', + 'reason_error' => 'Si è verificato un errore di autenticazione', + 'reconnect_cta' => 'Ricollega il tuo account per continuare a pianificare e pubblicare.', + 'button' => 'Ricollega account', + ], + + 'email_verification' => [ + 'subject' => 'Verifica il tuo indirizzo email', + 'preview' => 'Verifica il tuo indirizzo email.', + 'greeting' => 'Ciao :name,', + 'body' => 'Conferma il tuo indirizzo email facendo clic sul pulsante qui sotto:', + 'button' => 'Verifica email', + 'ignore' => 'Se non hai creato un account, puoi ignorare questa email.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name ti ha menzionato su TryPost', 'title' => ':name ti ha menzionato', 'intro' => ':name ti ha menzionato nel commento a un post.', - 'cta' => 'Visualizza commento', + 'button' => 'Visualizza commento', + ], + + 'password_reset' => [ + 'subject' => 'Reimposta la tua password', + 'preview' => 'Reimposta la tua password.', + 'greeting' => 'Ciao :name,', + 'body' => 'Abbiamo ricevuto una richiesta di reimpostazione della password. Fai clic sul pulsante per crearne una nuova:', + 'button' => 'Reimposta password', + 'expiry' => 'Questo link scade tra 60 minuti. Se non hai richiesto la reimpostazione, puoi ignorare questa email.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count post a rischio in :workspace|[0,*] :count post a rischio in :workspace', + 'title' => 'I post potrebbero non essere pubblicati', + 'heading' => 'I post potrebbero non essere pubblicati', + 'intro' => 'I seguenti account nello spazio di lavoro :workspace devono essere ricollegati prima che questi post pianificati possano essere pubblicati:', + 'posts_label' => '{1} :count post pianificato: :times UTC|[0,*] :count post pianificati: :times UTC', + 'reconnect_cta' => 'Ricollega subito questi account per non perdere i post pianificati.', + 'button' => 'Ricollega account', + ], + + 'post_publish_failed' => [ + 'subject' => 'La pubblicazione del tuo post in :workspace non è riuscita', + 'title' => 'La pubblicazione del tuo post non è riuscita', + 'preview' => 'Una o più piattaforme non sono riuscite a pubblicare.', + 'heading' => 'La pubblicazione del tuo post non è riuscita', + 'body' => 'Il tuo post pianificato nello spazio di lavoro :workspace non è stato pubblicato su una o più piattaforme.', + 'platforms_title' => 'Piattaforme non riuscite:', + 'button' => 'Vedi post', + ], + + 'post_published' => [ + 'subject' => 'Il tuo post è stato pubblicato in :workspace', + 'title' => 'Il tuo post è stato pubblicato', + 'preview' => 'Il tuo post è stato pubblicato correttamente.', + 'heading' => 'Il tuo post è stato pubblicato', + 'body' => 'Il tuo post nello spazio di lavoro :workspace è stato pubblicato correttamente.', + 'platforms_title' => 'Pubblicato su:', + 'view_post' => 'Vedi post', + 'button' => 'Vedi post', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook in pausa: :endpoint', + 'title' => 'Webhook messo in pausa dopo errori ripetuti', + 'preview' => 'Abbiamo messo in pausa un webhook dopo 5 errori di consegna consecutivi.', + 'heading' => 'Webhook messo in pausa dopo errori ripetuti', + 'body' => 'Abbiamo messo in pausa il webhook su :endpoint dopo 5 errori di consegna consecutivi. Controlla l\'endpoint e riattivalo dalla pagina dei dettagli del webhook.', + 'button' => 'Vedi webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count account deve essere ricollegato in :workspace|[2,*] :count account devono essere ricollegati in :workspace', + 'subject' => '{1} :count account deve essere ricollegato in :workspace|[0,*] :count account devono essere ricollegati in :workspace', 'title' => 'Alcuni account devono essere ricollegati', + 'heading' => 'Alcuni account devono essere ricollegati', 'intro' => 'I seguenti account social nel tuo workspace :workspace sono stati scollegati e devono essere ricollegati:', 'reasons_title' => 'Questo potrebbe essere accaduto perché:', 'reason_expired' => 'I token di accesso sono scaduti', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Ricollega questi account per continuare a programmare e pubblicare i post.', 'button' => 'Ricollega account', ], + + 'workspace_invite' => [ + 'subject' => 'Sei stato invitato a unirti a :account', + 'title' => 'Sei stato invitato a unirti a :account', + 'preview' => 'Sei stato invitato a unirti a :account', + 'heading' => 'Sei stato invitato!', + 'intro' => 'Sei stato invitato a collaborare nello spazio di lavoro :account.', + 'role' => 'Sei stato invitato come :role.', + 'button' => 'Accetta invito', + 'expiry' => 'Questo invito scade tra 7 giorni.', + ], + ]; diff --git a/lang/it/webhooks.php b/lang/it/webhooks.php index 62a995d3c..c791d06cb 100644 --- a/lang/it/webhooks.php +++ b/lang/it/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Evento del webhook reinviato.', 'tested' => 'Evento di test inviato.', ], - 'mail' => [ - 'paused_subject' => 'Webhook in pausa: :endpoint', - 'paused_title' => 'Webhook messo in pausa dopo errori ripetuti', - 'paused_preview' => 'Abbiamo messo in pausa un webhook dopo 5 errori di consegna consecutivi.', - 'paused_body' => 'Abbiamo messo in pausa il webhook su :endpoint dopo 5 errori di consegna consecutivi. Controlla l\'endpoint e riattivalo dalla pagina dei dettagli del webhook.', - 'paused_cta' => 'Vedi webhook', - ], ]; diff --git a/lang/ja/mail.php b/lang/ja/mail.php index caf7357f0..586d9e21d 100644 --- a/lang/ja/mail.php +++ b/lang/ja/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'オープンソースの SNS 投稿スケジュールツール', + 'manage_notifications' => '通知設定', + 'signoff' => 'よろしくお願いいたします。', + 'team' => 'TryPost チーム', + ], + + 'account_disconnected' => [ + 'subject' => ':workspace の :platform アカウントの再接続が必要です', + 'title' => ':platform アカウントの再接続が必要です', + 'preview' => ':workspace の :platform アカウントを再接続すると、投稿の予約を続けられます。', + 'heading' => 'アカウントが切断されました', + 'intro' => 'ワークスペース :workspace から :platform アカウント :account が切断されました。', + 'reasons_title' => '考えられる原因:', + 'reason_expired' => 'アクセストークンの有効期限が切れた', + 'reason_revoked' => 'TryPost のアクセス権を取り消した', + 'reason_error' => '認証エラーが発生した', + 'reconnect_cta' => 'アカウントを再接続すると、投稿の予約と公開を続けられます。', + 'button' => 'アカウントを再接続', + ], + + 'email_verification' => [ + 'subject' => 'メールアドレスを確認してください', + 'preview' => 'メールアドレスをご確認ください。', + 'greeting' => ':name さん、こんにちは。', + 'body' => '下のボタンからメールアドレスを確認してください。', + 'button' => 'メールアドレスを確認', + 'ignore' => 'アカウントを作成していない場合は、このメールを無視してください。', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name さんが TryPost であなたにメンションしました', 'title' => ':name さんがあなたにメンションしました', 'intro' => ':name さんが投稿のコメントであなたにメンションしました。', - 'cta' => 'コメントを表示', + 'button' => 'コメントを表示', + ], + + 'password_reset' => [ + 'subject' => 'パスワードを再設定してください', + 'preview' => 'パスワードを再設定してください。', + 'greeting' => ':name さん、こんにちは。', + 'body' => 'パスワード再設定のリクエストを受け取りました。下のボタンから新しいパスワードを設定してください。', + 'button' => 'パスワードを再設定', + 'expiry' => 'このリンクは 60 分で無効になります。心当たりがない場合は、このメールを無視してください。', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :workspace で :count 件の投稿が公開できない可能性があります|[0,*] :workspace で :count 件の投稿が公開できない可能性があります', + 'title' => '投稿が公開できない可能性があります', + 'heading' => '投稿が公開できない可能性があります', + 'intro' => '予約済みの投稿を公開するには、ワークスペース :workspace の次のアカウントを再接続する必要があります。', + 'posts_label' => '{1} :count 件の予約投稿: :times UTC|[0,*] :count 件の予約投稿: :times UTC', + 'reconnect_cta' => '予約投稿を逃さないよう、今すぐこれらのアカウントを再接続してください。', + 'button' => 'アカウントを再接続', + ], + + 'post_publish_failed' => [ + 'subject' => ':workspace で投稿の公開に失敗しました', + 'title' => '投稿の公開に失敗しました', + 'preview' => '1 つ以上のプラットフォームで公開に失敗しました。', + 'heading' => '投稿の公開に失敗しました', + 'body' => 'ワークスペース :workspace の予約投稿が、1 つ以上のプラットフォームで公開に失敗しました。', + 'platforms_title' => '失敗したプラットフォーム:', + 'button' => '投稿を見る', + ], + + 'post_published' => [ + 'subject' => ':workspace で投稿が公開されました', + 'title' => '投稿が公開されました', + 'preview' => '投稿が正常に公開されました。', + 'heading' => '投稿が公開されました', + 'body' => 'ワークスペース :workspace の投稿が正常に公開されました。', + 'platforms_title' => '公開先:', + 'view_post' => '投稿を見る', + 'button' => '投稿を見る', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhookを一時停止しました: :endpoint', + 'title' => '連続した失敗のためWebhookを一時停止しました', + 'preview' => '配信が5回連続で失敗したため、Webhookを一時停止しました。', + 'heading' => '連続した失敗のためWebhookを一時停止しました', + 'body' => ':endpoint のWebhookを、配信が5回連続で失敗したため一時停止しました。Endpointを確認し、Webhookの詳細ページから再度有効にしてください。', + 'button' => 'Webhookを見る', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :workspace で :count 件のアカウントを再接続する必要があります|[2,*] :workspace で :count 件のアカウントを再接続する必要があります', + 'subject' => '{1} :workspace で :count 件のアカウントを再接続する必要があります|[0,*] :workspace で :count 件のアカウントを再接続する必要があります', 'title' => 'アカウントの再接続が必要です', + 'heading' => 'アカウントの再接続が必要です', 'intro' => ':workspace ワークスペースの以下のソーシャルアカウントが接続解除されており、再接続が必要です:', 'reasons_title' => '次の理由が考えられます:', 'reason_expired' => 'アクセストークンの有効期限が切れた', @@ -19,4 +102,16 @@ 'reconnect_cta' => '投稿のスケジュールと公開を続けるには、これらのアカウントを再接続してください。', 'button' => 'アカウントを再接続', ], + + 'workspace_invite' => [ + 'subject' => ':account への招待が届いています', + 'title' => ':account への招待が届いています', + 'preview' => ':account への招待が届いています', + 'heading' => '招待が届いています', + 'intro' => 'ワークスペース :account での共同作業に招待されました。', + 'role' => ':role として招待されました。', + 'button' => '招待を承認', + 'expiry' => 'この招待は 7 日間有効です。', + ], + ]; diff --git a/lang/ja/webhooks.php b/lang/ja/webhooks.php index d882ba534..a9415b73e 100644 --- a/lang/ja/webhooks.php +++ b/lang/ja/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Webhookイベントを再送信しました。', 'tested' => 'テストイベントを送信しました。', ], - 'mail' => [ - 'paused_subject' => 'Webhookを一時停止しました: :endpoint', - 'paused_title' => '連続した失敗のためWebhookを一時停止しました', - 'paused_preview' => '配信が5回連続で失敗したため、Webhookを一時停止しました。', - 'paused_body' => ':endpoint のWebhookを、配信が5回連続で失敗したため一時停止しました。Endpointを確認し、Webhookの詳細ページから再度有効にしてください。', - 'paused_cta' => 'Webhookを見る', - ], ]; diff --git a/lang/ko/mail.php b/lang/ko/mail.php index e3b95a08f..671b71a36 100644 --- a/lang/ko/mail.php +++ b/lang/ko/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => '오픈소스 소셜 미디어 예약 발행 도구', + 'manage_notifications' => '알림 관리', + 'signoff' => '감사합니다.', + 'team' => 'TryPost 팀', + ], + + 'account_disconnected' => [ + 'subject' => ':workspace의 :platform 계정을 다시 연결해야 합니다', + 'title' => ':platform 계정을 다시 연결해야 합니다', + 'preview' => '게시물 예약을 계속하려면 :workspace의 :platform 계정을 다시 연결하세요.', + 'heading' => '계정 연결 해제됨', + 'intro' => '워크스페이스 :workspace에서 :platform 계정 :account의 연결이 해제되었습니다.', + 'reasons_title' => '다음과 같은 이유일 수 있습니다:', + 'reason_expired' => '액세스 토큰이 만료되었습니다', + 'reason_revoked' => 'TryPost의 접근 권한을 취소했습니다', + 'reason_error' => '인증 오류가 발생했습니다', + 'reconnect_cta' => '계정을 다시 연결하면 예약과 발행을 계속할 수 있습니다.', + 'button' => '계정 다시 연결', + ], + + 'email_verification' => [ + 'subject' => '이메일 주소를 인증하세요', + 'preview' => '이메일 주소를 인증해 주세요.', + 'greeting' => ':name님, 안녕하세요.', + 'body' => '아래 버튼을 눌러 이메일 주소를 확인해 주세요.', + 'button' => '이메일 인증', + 'ignore' => '계정을 만든 적이 없다면 이 메일을 무시하셔도 됩니다.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name님이 TryPost에서 회원님을 멘션했습니다', 'title' => ':name님이 회원님을 멘션했습니다', 'intro' => ':name님이 게시물 댓글에서 회원님을 멘션했습니다.', - 'cta' => '댓글 보기', + 'button' => '댓글 보기', + ], + + 'password_reset' => [ + 'subject' => '비밀번호를 재설정하세요', + 'preview' => '비밀번호를 재설정하세요.', + 'greeting' => ':name님, 안녕하세요.', + 'body' => '비밀번호 재설정 요청을 받았습니다. 아래 버튼을 눌러 새 비밀번호를 만드세요.', + 'button' => '비밀번호 재설정', + 'expiry' => '이 링크는 60분 후 만료됩니다. 요청한 적이 없다면 이 메일을 무시하셔도 됩니다.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :workspace에서 게시물 :count건이 발행되지 않을 수 있습니다|[0,*] :workspace에서 게시물 :count건이 발행되지 않을 수 있습니다', + 'title' => '게시물 발행이 실패할 수 있습니다', + 'heading' => '게시물 발행이 실패할 수 있습니다', + 'intro' => '예약한 게시물을 발행하려면 워크스페이스 :workspace의 다음 계정을 다시 연결해야 합니다:', + 'posts_label' => '{1} 예약 게시물 :count건: :times UTC|[0,*] 예약 게시물 :count건: :times UTC', + 'reconnect_cta' => '예약한 게시물을 놓치지 않도록 지금 계정을 다시 연결하세요.', + 'button' => '계정 다시 연결', + ], + + 'post_publish_failed' => [ + 'subject' => ':workspace에서 게시물 발행에 실패했습니다', + 'title' => '게시물 발행에 실패했습니다', + 'preview' => '하나 이상의 플랫폼에서 발행하지 못했습니다.', + 'heading' => '게시물 발행에 실패했습니다', + 'body' => '워크스페이스 :workspace의 예약 게시물이 하나 이상의 플랫폼에서 발행되지 않았습니다.', + 'platforms_title' => '실패한 플랫폼:', + 'button' => '게시물 보기', + ], + + 'post_published' => [ + 'subject' => ':workspace에서 게시물이 발행되었습니다', + 'title' => '게시물이 발행되었습니다', + 'preview' => '게시물이 정상적으로 발행되었습니다.', + 'heading' => '게시물이 발행되었습니다', + 'body' => '워크스페이스 :workspace의 게시물이 정상적으로 발행되었습니다.', + 'platforms_title' => '발행된 플랫폼:', + 'view_post' => '게시물 보기', + 'button' => '게시물 보기', + ], + + 'webhook_paused' => [ + 'subject' => '웹훅이 일시정지됨: :endpoint', + 'title' => '반복된 실패로 웹훅이 일시정지되었습니다', + 'preview' => '연속 5회 전달에 실패한 뒤 웹훅을 일시정지했습니다.', + 'heading' => '반복된 실패로 웹훅이 일시정지되었습니다', + 'body' => ':endpoint의 웹훅을 연속 5회 전달 실패 후 일시정지했습니다. Endpoint를 확인한 뒤 웹훅 상세 페이지에서 다시 사용하세요.', + 'button' => '웹훅 보기', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :workspace에서 :count개 계정을 재연결해야 합니다|[2,*] :workspace에서 :count개 계정을 재연결해야 합니다', + 'subject' => '{1} :workspace에서 :count개 계정을 재연결해야 합니다|[0,*] :workspace에서 :count개 계정을 재연결해야 합니다', 'title' => '계정 재연결 필요', + 'heading' => '계정 재연결 필요', 'intro' => ':workspace 워크스페이스의 다음 소셜 계정이 연결 해제되어 재연결이 필요합니다:', 'reasons_title' => '다음과 같은 이유로 발생했을 수 있습니다:', 'reason_expired' => '액세스 토큰 만료', @@ -19,4 +102,16 @@ 'reconnect_cta' => '게시물 예약 및 게시를 계속하려면 이 계정들을 재연결해 주세요.', 'button' => '계정 재연결', ], + + 'workspace_invite' => [ + 'subject' => ':account에 초대되었습니다', + 'title' => ':account에 초대되었습니다', + 'preview' => ':account에 초대되었습니다', + 'heading' => '초대를 받았습니다', + 'intro' => '워크스페이스 :account에서 함께 일하도록 초대되었습니다.', + 'role' => ':role 역할로 초대되었습니다.', + 'button' => '초대 수락', + 'expiry' => '이 초대는 7일 후 만료됩니다.', + ], + ]; diff --git a/lang/ko/webhooks.php b/lang/ko/webhooks.php index 8b94a392d..ca749ad78 100644 --- a/lang/ko/webhooks.php +++ b/lang/ko/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => '웹훅 이벤트를 다시 보냈습니다.', 'tested' => '테스트 이벤트를 보냈습니다.', ], - 'mail' => [ - 'paused_subject' => '웹훅이 일시정지됨: :endpoint', - 'paused_title' => '반복된 실패로 웹훅이 일시정지되었습니다', - 'paused_preview' => '연속 5회 전달에 실패한 뒤 웹훅을 일시정지했습니다.', - 'paused_body' => ':endpoint의 웹훅을 연속 5회 전달 실패 후 일시정지했습니다. Endpoint를 확인한 뒤 웹훅 상세 페이지에서 다시 사용하세요.', - 'paused_cta' => '웹훅 보기', - ], ]; diff --git a/lang/nl/mail.php b/lang/nl/mail.php index 5b37fb4ca..ee43afe95 100644 --- a/lang/nl/mail.php +++ b/lang/nl/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Open-source tool om social media in te plannen', + 'manage_notifications' => 'Meldingen beheren', + 'signoff' => 'Met vriendelijke groet,', + 'team' => 'Het TryPost-team', + ], + + 'account_disconnected' => [ + 'subject' => 'Je :platform-account in :workspace moet opnieuw worden verbonden', + 'title' => 'Je :platform-account moet opnieuw worden verbonden', + 'preview' => 'Verbind je :platform-account in :workspace opnieuw om posts te blijven inplannen.', + 'heading' => 'Account losgekoppeld', + 'intro' => 'Je :platform-account :account is losgekoppeld van de werkruimte :workspace.', + 'reasons_title' => 'Dit kan zijn gebeurd omdat:', + 'reason_expired' => 'Je toegangstoken is verlopen', + 'reason_revoked' => 'Je hebt de toegang van TryPost ingetrokken', + 'reason_error' => 'Er was een authenticatiefout', + 'reconnect_cta' => 'Verbind je account opnieuw om te blijven inplannen en publiceren.', + 'button' => 'Account opnieuw verbinden', + ], + + 'email_verification' => [ + 'subject' => 'Bevestig je e-mailadres', + 'preview' => 'Bevestig je e-mailadres.', + 'greeting' => 'Hallo :name,', + 'body' => 'Bevestig je e-mailadres via de knop hieronder:', + 'button' => 'E-mailadres bevestigen', + 'ignore' => 'Als je geen account hebt aangemaakt, kun je deze e-mail negeren.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name heeft je genoemd op TryPost', 'title' => ':name heeft je genoemd', 'intro' => ':name heeft je genoemd in een reactie op een post.', - 'cta' => 'Reactie bekijken', + 'button' => 'Reactie bekijken', + ], + + 'password_reset' => [ + 'subject' => 'Stel je wachtwoord opnieuw in', + 'preview' => 'Stel je wachtwoord opnieuw in.', + 'greeting' => 'Hallo :name,', + 'body' => 'We hebben een verzoek ontvangen om je wachtwoord opnieuw in te stellen. Klik op de knop om een nieuw wachtwoord te maken:', + 'button' => 'Wachtwoord opnieuw instellen', + 'expiry' => 'Deze link verloopt over 60 minuten. Als je hier niet om hebt gevraagd, kun je deze e-mail negeren.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count post loopt risico in :workspace|[0,*] :count posts lopen risico in :workspace', + 'title' => 'Posts kunnen mislukken', + 'heading' => 'Posts kunnen mislukken', + 'intro' => 'De volgende accounts in de werkruimte :workspace moeten opnieuw worden verbonden voordat deze ingeplande posts kunnen worden gepubliceerd:', + 'posts_label' => '{1} :count post ingepland: :times UTC|[0,*] :count posts ingepland: :times UTC', + 'reconnect_cta' => 'Verbind deze accounts nu opnieuw zodat je ingeplande posts niet worden gemist.', + 'button' => 'Accounts opnieuw verbinden', + ], + + 'post_publish_failed' => [ + 'subject' => 'Je post in :workspace kon niet worden gepubliceerd', + 'title' => 'Je post kon niet worden gepubliceerd', + 'preview' => 'Een of meer platforms konden niet publiceren.', + 'heading' => 'Je post kon niet worden gepubliceerd', + 'body' => 'Je ingeplande post in de werkruimte :workspace kon op een of meer platforms niet worden gepubliceerd.', + 'platforms_title' => 'Mislukte platforms:', + 'button' => 'Post bekijken', + ], + + 'post_published' => [ + 'subject' => 'Je post is gepubliceerd in :workspace', + 'title' => 'Je post is gepubliceerd', + 'preview' => 'Je post is succesvol gepubliceerd.', + 'heading' => 'Je post is gepubliceerd', + 'body' => 'Je post in de werkruimte :workspace is succesvol gepubliceerd.', + 'platforms_title' => 'Gepubliceerd op:', + 'view_post' => 'Post bekijken', + 'button' => 'Post bekijken', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook gepauzeerd: :endpoint', + 'title' => 'Webhook gepauzeerd na herhaalde fouten', + 'preview' => 'We hebben een webhook gepauzeerd na 5 opeenvolgende afleverfouten.', + 'heading' => 'Webhook gepauzeerd na herhaalde fouten', + 'body' => 'We hebben de webhook op :endpoint gepauzeerd na 5 opeenvolgende afleverfouten. Controleer het endpoint en schakel het weer in op de webhookdetailpagina.', + 'button' => 'Webhook bekijken', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count account moet opnieuw worden gekoppeld in :workspace|[2,*] :count accounts moeten opnieuw worden gekoppeld in :workspace', + 'subject' => '{1} :count account moet opnieuw worden gekoppeld in :workspace|[0,*] :count accounts moeten opnieuw worden gekoppeld in :workspace', 'title' => 'Accounts moeten opnieuw worden gekoppeld', + 'heading' => 'Accounts moeten opnieuw worden gekoppeld', 'intro' => 'De volgende social accounts in je workspace :workspace zijn losgekoppeld en moeten opnieuw worden gekoppeld:', 'reasons_title' => 'Dit kan gebeurd zijn omdat:', 'reason_expired' => 'Toegangstokens zijn verlopen', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Koppel deze accounts opnieuw om posts te blijven plannen en publiceren.', 'button' => 'Accounts opnieuw koppelen', ], + + 'workspace_invite' => [ + 'subject' => 'Je bent uitgenodigd voor :account', + 'title' => 'Je bent uitgenodigd voor :account', + 'preview' => 'Je bent uitgenodigd voor :account', + 'heading' => 'Je bent uitgenodigd!', + 'intro' => 'Je bent uitgenodigd om samen te werken in de werkruimte :account.', + 'role' => 'Je bent uitgenodigd als :role.', + 'button' => 'Uitnodiging accepteren', + 'expiry' => 'Deze uitnodiging verloopt over 7 dagen.', + ], + ]; diff --git a/lang/nl/webhooks.php b/lang/nl/webhooks.php index 08902d8c0..28a9bd22b 100644 --- a/lang/nl/webhooks.php +++ b/lang/nl/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Webhookgebeurtenis opnieuw verstuurd.', 'tested' => 'Testgebeurtenis verstuurd.', ], - 'mail' => [ - 'paused_subject' => 'Webhook gepauzeerd: :endpoint', - 'paused_title' => 'Webhook gepauzeerd na herhaalde fouten', - 'paused_preview' => 'We hebben een webhook gepauzeerd na 5 opeenvolgende afleverfouten.', - 'paused_body' => 'We hebben de webhook op :endpoint gepauzeerd na 5 opeenvolgende afleverfouten. Controleer het endpoint en schakel het weer in op de webhookdetailpagina.', - 'paused_cta' => 'Webhook bekijken', - ], ]; diff --git a/lang/pl/mail.php b/lang/pl/mail.php index dfe4cfc26..c6af1fc62 100644 --- a/lang/pl/mail.php +++ b/lang/pl/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Otwartoźródłowe narzędzie do planowania postów w social mediach', + 'manage_notifications' => 'Zarządzaj powiadomieniami', + 'signoff' => 'Pozdrawiamy,', + 'team' => 'Zespół TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Twoje konto :platform w :workspace wymaga ponownego połączenia', + 'title' => 'Twoje konto :platform wymaga ponownego połączenia', + 'preview' => 'Połącz ponownie konto :platform w :workspace, aby dalej planować posty.', + 'heading' => 'Konto rozłączone', + 'intro' => 'Twoje konto :platform :account zostało rozłączone z przestrzenią roboczą :workspace.', + 'reasons_title' => 'Mogło się to stać, ponieważ:', + 'reason_expired' => 'Twój token dostępu wygasł', + 'reason_revoked' => 'Cofnięto dostęp dla TryPost', + 'reason_error' => 'Wystąpił błąd uwierzytelniania', + 'reconnect_cta' => 'Połącz konto ponownie, aby dalej planować i publikować posty.', + 'button' => 'Połącz konto ponownie', + ], + + 'email_verification' => [ + 'subject' => 'Potwierdź swój adres e-mail', + 'preview' => 'Potwierdź swój adres e-mail.', + 'greeting' => 'Cześć :name,', + 'body' => 'Potwierdź swój adres e-mail, klikając przycisk poniżej:', + 'button' => 'Potwierdź e-mail', + 'ignore' => 'Jeśli nie zakładałeś konta, możesz zignorować tę wiadomość.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name wspomniał o Tobie w TryPost', 'title' => ':name wspomniał o Tobie', 'intro' => ':name wspomniał o Tobie w komentarzu do posta.', - 'cta' => 'Zobacz komentarz', + 'button' => 'Zobacz komentarz', + ], + + 'password_reset' => [ + 'subject' => 'Zresetuj hasło', + 'preview' => 'Zresetuj hasło.', + 'greeting' => 'Cześć :name,', + 'body' => 'Otrzymaliśmy prośbę o zresetowanie hasła. Kliknij przycisk, aby ustawić nowe:', + 'button' => 'Zresetuj hasło', + 'expiry' => 'Ten link wygasa za 60 minut. Jeśli nie prosiłeś o reset hasła, możesz zignorować tę wiadomość.', + ], + + 'post_at_risk' => [ + 'subject' => ':count post jest zagrożony w :workspace|:count posty są zagrożone w :workspace|:count postów jest zagrożonych w :workspace', + 'title' => 'Posty mogą się nie opublikować', + 'heading' => 'Posty mogą się nie opublikować', + 'intro' => 'Poniższe konta w przestrzeni roboczej :workspace wymagają ponownego połączenia, zanim te zaplanowane posty będą mogły zostać opublikowane:', + 'posts_label' => ':count zaplanowany post: :times UTC|:count zaplanowane posty: :times UTC|:count zaplanowanych postów: :times UTC', + 'reconnect_cta' => 'Połącz te konta ponownie już teraz, aby nie przegapić zaplanowanych postów.', + 'button' => 'Połącz konta ponownie', + ], + + 'post_publish_failed' => [ + 'subject' => 'Nie udało się opublikować posta w :workspace', + 'title' => 'Nie udało się opublikować posta', + 'preview' => 'Co najmniej jedna platforma nie opublikowała posta.', + 'heading' => 'Nie udało się opublikować posta', + 'body' => 'Twój zaplanowany post w przestrzeni roboczej :workspace nie został opublikowany na co najmniej jednej platformie.', + 'platforms_title' => 'Platformy z błędem:', + 'button' => 'Zobacz post', + ], + + 'post_published' => [ + 'subject' => 'Twój post został opublikowany w :workspace', + 'title' => 'Twój post został opublikowany', + 'preview' => 'Twój post został opublikowany pomyślnie.', + 'heading' => 'Twój post został opublikowany', + 'body' => 'Twój post w przestrzeni roboczej :workspace został opublikowany pomyślnie.', + 'platforms_title' => 'Opublikowano na:', + 'view_post' => 'Zobacz post', + 'button' => 'Zobacz post', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook wstrzymany: :endpoint', + 'title' => 'Webhook wstrzymany po powtarzających się błędach', + 'preview' => 'Wstrzymaliśmy webhook po 5 kolejnych błędach dostarczenia.', + 'heading' => 'Webhook wstrzymany po powtarzających się błędach', + 'body' => 'Wstrzymaliśmy webhook pod adresem :endpoint po 5 kolejnych błędach dostarczenia. Sprawdź endpoint i włącz go ponownie na stronie szczegółów webhooka.', + 'button' => 'Zobacz webhook', ], 'workspace_connections_disconnected' => [ 'subject' => ':count konto wymaga ponownego połączenia w przestrzeni roboczej :workspace|:count konta wymagają ponownego połączenia w przestrzeni roboczej :workspace|:count kont wymaga ponownego połączenia w przestrzeni roboczej :workspace', 'title' => 'Konta wymagają ponownego połączenia', + 'heading' => 'Konta wymagają ponownego połączenia', 'intro' => 'Następujące konta społecznościowe w Twojej przestrzeni roboczej :workspace zostały rozłączone i wymagają ponownego połączenia:', 'reasons_title' => 'Mogło się to zdarzyć, ponieważ:', 'reason_expired' => 'Tokeny dostępu wygasły', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Połącz te konta ponownie, aby kontynuować planowanie i publikowanie postów.', 'button' => 'Połącz konta ponownie', ], + + 'workspace_invite' => [ + 'subject' => 'Zaproszono Cię do :account', + 'title' => 'Zaproszono Cię do :account', + 'preview' => 'Zaproszono Cię do :account', + 'heading' => 'Masz zaproszenie!', + 'intro' => 'Zaproszono Cię do współpracy w przestrzeni roboczej :account.', + 'role' => 'Zaproszono Cię jako :role.', + 'button' => 'Przyjmij zaproszenie', + 'expiry' => 'To zaproszenie wygasa za 7 dni.', + ], + ]; diff --git a/lang/pl/webhooks.php b/lang/pl/webhooks.php index 850512018..7ba9ed03c 100644 --- a/lang/pl/webhooks.php +++ b/lang/pl/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Zdarzenie webhooka wysłane ponownie.', 'tested' => 'Wysłano zdarzenie testowe.', ], - 'mail' => [ - 'paused_subject' => 'Webhook wstrzymany: :endpoint', - 'paused_title' => 'Webhook wstrzymany po powtarzających się błędach', - 'paused_preview' => 'Wstrzymaliśmy webhook po 5 kolejnych błędach dostarczenia.', - 'paused_body' => 'Wstrzymaliśmy webhook pod adresem :endpoint po 5 kolejnych błędach dostarczenia. Sprawdź endpoint i włącz go ponownie na stronie szczegółów webhooka.', - 'paused_cta' => 'Zobacz webhook', - ], ]; diff --git a/lang/pt-BR/mail.php b/lang/pt-BR/mail.php index 540df957e..7b2eeb0ea 100644 --- a/lang/pt-BR/mail.php +++ b/lang/pt-BR/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Ferramenta open-source de agendamento para redes sociais', + 'manage_notifications' => 'Gerenciar notificações', + 'signoff' => 'Atenciosamente,', + 'team' => 'A equipe TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Sua conta do :platform em :workspace precisa ser reconectada', + 'title' => 'Sua conta do :platform precisa ser reconectada', + 'preview' => 'Reconecte sua conta do :platform em :workspace para continuar agendando publicações.', + 'heading' => 'Conta desconectada', + 'intro' => 'Sua conta do :platform :account foi desconectada da área de trabalho :workspace.', + 'reasons_title' => 'Isso pode ter acontecido porque:', + 'reason_expired' => 'Seu token de acesso expirou', + 'reason_revoked' => 'Você revogou o acesso do TryPost', + 'reason_error' => 'Ocorreu um erro de autenticação', + 'reconnect_cta' => 'Reconecte sua conta para continuar agendando e publicando.', + 'button' => 'Reconectar conta', + ], + + 'email_verification' => [ + 'subject' => 'Confirme seu endereço de e-mail', + 'preview' => 'Confirme seu endereço de e-mail.', + 'greeting' => 'Olá :name,', + 'body' => 'Confirme seu endereço de e-mail clicando no botão abaixo:', + 'button' => 'Confirmar e-mail', + 'ignore' => 'Se você não criou uma conta, pode ignorar este e-mail com segurança.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name mencionou você no TryPost', 'title' => ':name mencionou você', 'intro' => ':name mencionou você num comentário.', - 'cta' => 'Ver comentário', + 'button' => 'Ver comentário', + ], + + 'password_reset' => [ + 'subject' => 'Redefina sua senha', + 'preview' => 'Redefina sua senha.', + 'greeting' => 'Olá :name,', + 'body' => 'Recebemos um pedido para redefinir sua senha. Clique no botão abaixo para criar uma nova:', + 'button' => 'Redefinir senha', + 'expiry' => 'Este link expira em 60 minutos. Se você não pediu a redefinição, pode ignorar este e-mail com segurança.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count publicação em risco em :workspace|[0,*] :count publicações em risco em :workspace', + 'title' => 'Publicações podem falhar', + 'heading' => 'Publicações podem falhar', + 'intro' => 'As contas a seguir na área de trabalho :workspace precisam ser reconectadas antes que estas publicações agendadas possam ir ao ar:', + 'posts_label' => '{1} :count publicação agendada: :times UTC|[0,*] :count publicações agendadas: :times UTC', + 'reconnect_cta' => 'Reconecte estas contas agora para não perder suas publicações agendadas.', + 'button' => 'Reconectar contas', + ], + + 'post_publish_failed' => [ + 'subject' => 'Sua publicação falhou em :workspace', + 'title' => 'Sua publicação falhou', + 'preview' => 'Uma ou mais plataformas falharam ao publicar.', + 'heading' => 'Sua publicação falhou', + 'body' => 'Sua publicação agendada na área de trabalho :workspace falhou em uma ou mais plataformas.', + 'platforms_title' => 'Plataformas com falha:', + 'button' => 'Ver publicação', + ], + + 'post_published' => [ + 'subject' => 'Sua publicação foi ao ar em :workspace', + 'title' => 'Sua publicação foi ao ar', + 'preview' => 'Sua publicação foi publicada com sucesso.', + 'heading' => 'Sua publicação foi ao ar', + 'body' => 'Sua publicação na área de trabalho :workspace foi publicada com sucesso.', + 'platforms_title' => 'Publicada em:', + 'view_post' => 'Ver publicação', + 'button' => 'Ver publicação', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook pausado: :endpoint', + 'title' => 'Webhook pausado após falhas repetidas', + 'preview' => 'Pausamos um webhook após 5 falhas consecutivas de entrega.', + 'heading' => 'Webhook pausado após falhas repetidas', + 'body' => 'Pausamos o webhook em :endpoint após 5 falhas consecutivas de entrega. Revise o endpoint e reative-o na página de detalhes do webhook.', + 'button' => 'Ver webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count conta precisa ser reconectada em :workspace|[2,*] :count contas precisam ser reconectadas em :workspace', + 'subject' => '{1} :count conta precisa ser reconectada em :workspace|[0,*] :count contas precisam ser reconectadas em :workspace', 'title' => 'Contas Precisam ser Reconectadas', + 'heading' => 'Contas Precisam ser Reconectadas', 'intro' => 'As seguintes contas de redes sociais no seu workspace :workspace foram desconectadas e precisam ser reconectadas:', 'reasons_title' => 'Isso pode ter acontecido porque:', 'reason_expired' => 'Os tokens de acesso expiraram', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Por favor, reconecte essas contas para continuar agendando e publicando posts.', 'button' => 'Reconectar Contas', ], + + 'workspace_invite' => [ + 'subject' => 'Você foi convidado para o :account', + 'title' => 'Você foi convidado para o :account', + 'preview' => 'Você foi convidado para o :account', + 'heading' => 'Você foi convidado!', + 'intro' => 'Você foi convidado para colaborar na área de trabalho :account.', + 'role' => 'Você foi convidado como :role.', + 'button' => 'Aceitar convite', + 'expiry' => 'Este convite expira em 7 dias.', + ], + ]; diff --git a/lang/pt-BR/webhooks.php b/lang/pt-BR/webhooks.php index edc96768a..05c0c20d6 100644 --- a/lang/pt-BR/webhooks.php +++ b/lang/pt-BR/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Evento do webhook reenviado.', 'tested' => 'Evento de teste enviado.', ], - 'mail' => [ - 'paused_subject' => 'Webhook pausado: :endpoint', - 'paused_title' => 'Webhook pausado após falhas repetidas', - 'paused_preview' => 'Pausamos um webhook após 5 falhas consecutivas de entrega.', - 'paused_body' => 'Pausamos o webhook em :endpoint após 5 falhas consecutivas de entrega. Revise o endpoint e reative-o na página de detalhes do webhook.', - 'paused_cta' => 'Ver webhook', - ], ]; diff --git a/lang/ru/mail.php b/lang/ru/mail.php index 14e1b5213..8edf59dfb 100644 --- a/lang/ru/mail.php +++ b/lang/ru/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Открытый инструмент для планирования постов в соцсетях', + 'manage_notifications' => 'Управлять уведомлениями', + 'signoff' => 'С уважением,', + 'team' => 'Команда TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Аккаунт :platform в :workspace нужно переподключить', + 'title' => 'Аккаунт :platform нужно переподключить', + 'preview' => 'Переподключите аккаунт :platform в :workspace, чтобы продолжить планировать посты.', + 'heading' => 'Аккаунт отключён', + 'intro' => 'Ваш аккаунт :platform :account был отключён от рабочего пространства :workspace.', + 'reasons_title' => 'Это могло произойти потому, что:', + 'reason_expired' => 'Срок действия токена доступа истёк', + 'reason_revoked' => 'Вы отозвали доступ у TryPost', + 'reason_error' => 'Произошла ошибка аутентификации', + 'reconnect_cta' => 'Переподключите аккаунт, чтобы продолжить планировать и публиковать посты.', + 'button' => 'Переподключить аккаунт', + ], + + 'email_verification' => [ + 'subject' => 'Подтвердите адрес электронной почты', + 'preview' => 'Подтвердите адрес электронной почты.', + 'greeting' => 'Привет, :name!', + 'body' => 'Подтвердите адрес электронной почты, нажав кнопку ниже:', + 'button' => 'Подтвердить адрес', + 'ignore' => 'Если вы не создавали аккаунт, просто проигнорируйте это письмо.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name упомянул(а) вас в TryPost', 'title' => ':name упомянул(а) вас', 'intro' => ':name упомянул(а) вас в комментарии к посту.', - 'cta' => 'Посмотреть комментарий', + 'button' => 'Посмотреть комментарий', + ], + + 'password_reset' => [ + 'subject' => 'Сбросьте пароль', + 'preview' => 'Сбросьте пароль.', + 'greeting' => 'Привет, :name!', + 'body' => 'Мы получили запрос на сброс пароля. Нажмите кнопку, чтобы задать новый:', + 'button' => 'Сбросить пароль', + 'expiry' => 'Ссылка действует 60 минут. Если вы не запрашивали сброс, просто проигнорируйте это письмо.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count пост под угрозой в :workspace|[2,4] :count поста под угрозой в :workspace|[0,*] :count постов под угрозой в :workspace', + 'title' => 'Посты могут не опубликоваться', + 'heading' => 'Посты могут не опубликоваться', + 'intro' => 'Следующие аккаунты в рабочем пространстве :workspace нужно переподключить, чтобы эти запланированные посты опубликовались:', + 'posts_label' => '{1} :count запланированный пост: :times UTC|[2,4] :count запланированных поста: :times UTC|[0,*] :count запланированных постов: :times UTC', + 'reconnect_cta' => 'Переподключите эти аккаунты сейчас, чтобы не пропустить запланированные посты.', + 'button' => 'Переподключить аккаунты', + ], + + 'post_publish_failed' => [ + 'subject' => 'Не удалось опубликовать пост в :workspace', + 'title' => 'Не удалось опубликовать пост', + 'preview' => 'Одна или несколько платформ не смогли опубликовать пост.', + 'heading' => 'Не удалось опубликовать пост', + 'body' => 'Ваш запланированный пост в рабочем пространстве :workspace не опубликовался на одной или нескольких платформах.', + 'platforms_title' => 'Платформы с ошибкой:', + 'button' => 'Открыть пост', + ], + + 'post_published' => [ + 'subject' => 'Ваш пост опубликован в :workspace', + 'title' => 'Ваш пост опубликован', + 'preview' => 'Ваш пост успешно опубликован.', + 'heading' => 'Ваш пост опубликован', + 'body' => 'Ваш пост в рабочем пространстве :workspace успешно опубликован.', + 'platforms_title' => 'Опубликовано в:', + 'view_post' => 'Открыть пост', + 'button' => 'Открыть пост', + ], + + 'webhook_paused' => [ + 'subject' => 'Вебхук приостановлен: :endpoint', + 'title' => 'Вебхук приостановлен после повторных ошибок', + 'preview' => 'Мы приостановили вебхук после 5 подряд неудачных доставок.', + 'heading' => 'Вебхук приостановлен после повторных ошибок', + 'body' => 'Мы приостановили вебхук на :endpoint после 5 подряд неудачных доставок. Проверьте endpoint и включите его снова на странице вебхука.', + 'button' => 'Открыть вебхук', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} Нужно переподключить :count аккаунт в :workspace|[2,4] Нужно переподключить :count аккаунта в :workspace|[5,*] Нужно переподключить :count аккаунтов в :workspace', + 'subject' => '{1} Нужно переподключить :count аккаунт в :workspace|[2,4] Нужно переподключить :count аккаунта в :workspace|[0,*] Нужно переподключить :count аккаунтов в :workspace', 'title' => 'Аккаунты требуют переподключения', + 'heading' => 'Аккаунты требуют переподключения', 'intro' => 'Следующие социальные аккаунты в рабочем пространстве :workspace были отключены и требуют переподключения:', 'reasons_title' => 'Это могло произойти по следующим причинам:', 'reason_expired' => 'Истёк срок действия токенов доступа', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Пожалуйста, переподключите эти аккаунты, чтобы продолжить планировать и публиковать посты.', 'button' => 'Переподключить аккаунты', ], + + 'workspace_invite' => [ + 'subject' => 'Вас пригласили присоединиться к :account', + 'title' => 'Вас пригласили присоединиться к :account', + 'preview' => 'Вас пригласили присоединиться к :account', + 'heading' => 'Вас пригласили!', + 'intro' => 'Вас пригласили к совместной работе в рабочем пространстве :account.', + 'role' => 'Вас пригласили как :role.', + 'button' => 'Принять приглашение', + 'expiry' => 'Приглашение действует 7 дней.', + ], + ]; diff --git a/lang/ru/webhooks.php b/lang/ru/webhooks.php index 9ea6b048d..cabe32312 100644 --- a/lang/ru/webhooks.php +++ b/lang/ru/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Событие вебхука отправлено снова.', 'tested' => 'Тестовое событие отправлено.', ], - 'mail' => [ - 'paused_subject' => 'Вебхук приостановлен: :endpoint', - 'paused_title' => 'Вебхук приостановлен после повторных ошибок', - 'paused_preview' => 'Мы приостановили вебхук после 5 подряд неудачных доставок.', - 'paused_body' => 'Мы приостановили вебхук на :endpoint после 5 подряд неудачных доставок. Проверьте endpoint и включите его снова на странице вебхука.', - 'paused_cta' => 'Открыть вебхук', - ], ]; diff --git a/lang/tr/mail.php b/lang/tr/mail.php index 8805d1db4..d29327096 100644 --- a/lang/tr/mail.php +++ b/lang/tr/mail.php @@ -3,16 +3,97 @@ declare(strict_types=1); return [ - 'mentioned' => [ + + 'layout' => [ + 'tagline' => 'Sosyal medya paylaşımlarını planlamak için açık kaynaklı araç', + 'manage_notifications' => 'Bildirimleri yönet', + 'signoff' => 'Saygılarımızla,', + 'team' => 'TryPost Ekibi', + ], + + 'account_disconnected' => [ + 'subject' => ':workspace çalışma alanındaki :platform hesabının yeniden bağlanması gerekiyor', + 'title' => ':platform hesabının yeniden bağlanması gerekiyor', + 'preview' => 'Gönderi planlamaya devam etmek için :workspace çalışma alanındaki :platform hesabını yeniden bağla.', + 'heading' => 'Hesap bağlantısı kesildi', + 'intro' => ':platform hesabın :account, :workspace çalışma alanından koptu.', + 'reasons_title' => 'Bunun nedeni şunlar olabilir:', + 'reason_expired' => 'Erişim jetonunun süresi doldu', + 'reason_revoked' => 'TryPost erişimini iptal ettin', + 'reason_error' => 'Bir kimlik doğrulama hatası oluştu', + 'reconnect_cta' => 'Planlamaya ve paylaşmaya devam etmek için hesabını yeniden bağla.', + 'button' => 'Hesabı yeniden bağla', + ], + + 'email_verification' => [ + 'subject' => 'E-posta adresini doğrula', + 'preview' => 'Lütfen e-posta adresini doğrula.', + 'greeting' => 'Merhaba :name,', + 'body' => 'Aşağıdaki düğmeye tıklayarak e-posta adresini onayla:', + 'button' => 'E-postayı doğrula', + 'ignore' => 'Bir hesap oluşturmadıysan bu e-postayı yok sayabilirsin.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name sizden TryPost\'ta bahsetti', 'title' => ':name sizden bahsetti', 'intro' => ':name bir gönderi yorumunda sizden bahsetti.', - 'cta' => 'Yorumu görüntüle', + 'button' => 'Yorumu görüntüle', + ], + + 'password_reset' => [ + 'subject' => 'Parolanı sıfırla', + 'preview' => 'Parolanı sıfırla.', + 'greeting' => 'Merhaba :name,', + 'body' => 'Parola sıfırlama isteği aldık. Yeni bir parola oluşturmak için aşağıdaki düğmeye tıkla:', + 'button' => 'Parolayı sıfırla', + 'expiry' => 'Bu bağlantı 60 dakika içinde geçersiz olur. İsteği sen göndermediysen bu e-postayı yok sayabilirsin.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :workspace çalışma alanında :count gönderi risk altında|[0,*] :workspace çalışma alanında :count gönderi risk altında', + 'title' => 'Gönderiler paylaşılamayabilir', + 'heading' => 'Gönderiler paylaşılamayabilir', + 'intro' => 'Planlanan bu gönderilerin paylaşılabilmesi için :workspace çalışma alanındaki şu hesapların yeniden bağlanması gerekiyor:', + 'posts_label' => '{1} :count gönderi planlandı: :times UTC|[0,*] :count gönderi planlandı: :times UTC', + 'reconnect_cta' => 'Planladığın gönderileri kaçırmamak için bu hesapları hemen yeniden bağla.', + 'button' => 'Hesapları yeniden bağla', + ], + + 'post_publish_failed' => [ + 'subject' => ':workspace çalışma alanında gönderin paylaşılamadı', + 'title' => 'Gönderin paylaşılamadı', + 'preview' => 'Bir veya daha fazla platformda paylaşım başarısız oldu.', + 'heading' => 'Gönderin paylaşılamadı', + 'body' => ':workspace çalışma alanında planladığın gönderi, bir veya daha fazla platformda paylaşılamadı.', + 'platforms_title' => 'Başarısız platformlar:', + 'button' => 'Gönderiyi gör', + ], + + 'post_published' => [ + 'subject' => ':workspace çalışma alanında gönderin paylaşıldı', + 'title' => 'Gönderin paylaşıldı', + 'preview' => 'Gönderin başarıyla paylaşıldı.', + 'heading' => 'Gönderin paylaşıldı', + 'body' => ':workspace çalışma alanındaki gönderin başarıyla paylaşıldı.', + 'platforms_title' => 'Şurada paylaşıldı:', + 'view_post' => 'Gönderiyi gör', + 'button' => 'Gönderiyi gör', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook duraklatıldı: :endpoint', + 'title' => 'Webhook tekrarlanan hatalardan sonra duraklatıldı', + 'preview' => 'Üst üste 5 teslim hatasından sonra bir webhooku duraklattık.', + 'heading' => 'Webhook tekrarlanan hatalardan sonra duraklatıldı', + 'body' => ':endpoint adresindeki webhooku üst üste 5 teslim hatasından sonra duraklattık. Endpointi gözden geçirin ve webhook ayrıntı sayfasından yeniden etkinleştirin.', + 'button' => 'Webhooku gör', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :workspace çalışma alanında :count hesabın yeniden bağlanması gerekiyor|[2,*] :workspace çalışma alanında :count hesabın yeniden bağlanması gerekiyor', + 'subject' => '{1} :workspace çalışma alanında :count hesabın yeniden bağlanması gerekiyor|[0,*] :workspace çalışma alanında :count hesabın yeniden bağlanması gerekiyor', 'title' => 'Hesapların Yeniden Bağlanması Gerekiyor', + 'heading' => 'Hesapların Yeniden Bağlanması Gerekiyor', 'intro' => ':workspace çalışma alanınızdaki aşağıdaki sosyal hesapların bağlantısı kesildi ve yeniden bağlanması gerekiyor:', 'reasons_title' => 'Bu şu nedenlerle olmuş olabilir:', 'reason_expired' => 'Erişim tokenlarının süresi doldu', @@ -21,4 +102,16 @@ 'reconnect_cta' => 'Gönderi zamanlamaya ve yayınlamaya devam etmek için lütfen bu hesapları yeniden bağlayın.', 'button' => 'Hesapları Yeniden Bağla', ], + + 'workspace_invite' => [ + 'subject' => ':account hesabına katılmaya davet edildin', + 'title' => ':account hesabına katılmaya davet edildin', + 'preview' => ':account hesabına katılmaya davet edildin', + 'heading' => 'Davet edildin!', + 'intro' => ':account çalışma alanında birlikte çalışmaya davet edildin.', + 'role' => ':role olarak davet edildin.', + 'button' => 'Daveti kabul et', + 'expiry' => 'Bu davet 7 gün içinde sona erer.', + ], + ]; diff --git a/lang/tr/webhooks.php b/lang/tr/webhooks.php index e7754c624..3446ebc59 100644 --- a/lang/tr/webhooks.php +++ b/lang/tr/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Webhook olayı yeniden gönderildi.', 'tested' => 'Test olayı gönderildi.', ], - 'mail' => [ - 'paused_subject' => 'Webhook duraklatıldı: :endpoint', - 'paused_title' => 'Webhook tekrarlanan hatalardan sonra duraklatıldı', - 'paused_preview' => 'Üst üste 5 teslim hatasından sonra bir webhooku duraklattık.', - 'paused_body' => ':endpoint adresindeki webhooku üst üste 5 teslim hatasından sonra duraklattık. Endpointi gözden geçirin ve webhook ayrıntı sayfasından yeniden etkinleştirin.', - 'paused_cta' => 'Webhooku gör', - ], ]; diff --git a/lang/uk/mail.php b/lang/uk/mail.php index e81e5b741..c31dc63e0 100644 --- a/lang/uk/mail.php +++ b/lang/uk/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => 'Відкритий інструмент для планування дописів у соцмережах', + 'manage_notifications' => 'Керувати сповіщеннями', + 'signoff' => 'З повагою,', + 'team' => 'Команда TryPost', + ], + + 'account_disconnected' => [ + 'subject' => 'Обліковий запис :platform у :workspace потребує повторного підключення', + 'title' => 'Обліковий запис :platform потребує повторного підключення', + 'preview' => 'Підключіть обліковий запис :platform у :workspace повторно, щоб далі планувати дописи.', + 'heading' => 'Обліковий запис відключено', + 'intro' => 'Ваш обліковий запис :platform :account було відключено від робочого простору :workspace.', + 'reasons_title' => 'Це могло статися, тому що:', + 'reason_expired' => 'Термін дії токена доступу минув', + 'reason_revoked' => 'Ви скасували доступ для TryPost', + 'reason_error' => 'Сталася помилка автентифікації', + 'reconnect_cta' => 'Підключіть обліковий запис повторно, щоб далі планувати й публікувати дописи.', + 'button' => 'Підключити повторно', + ], + + 'email_verification' => [ + 'subject' => 'Підтвердьте свою електронну адресу', + 'preview' => 'Підтвердьте свою електронну адресу.', + 'greeting' => 'Привіт, :name!', + 'body' => 'Підтвердьте свою електронну адресу, натиснувши кнопку нижче:', + 'button' => 'Підтвердити адресу', + 'ignore' => 'Якщо ви не створювали обліковий запис, просто проігноруйте цей лист.', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name згадав(ла) вас у TryPost', 'title' => ':name згадав(ла) вас', 'intro' => ':name згадав(ла) вас у коментарі до поста.', - 'cta' => 'Переглянути коментар', + 'button' => 'Переглянути коментар', + ], + + 'password_reset' => [ + 'subject' => 'Скиньте свій пароль', + 'preview' => 'Скиньте свій пароль.', + 'greeting' => 'Привіт, :name!', + 'body' => 'Ми отримали запит на скидання пароля. Натисніть кнопку, щоб створити новий:', + 'button' => 'Скинути пароль', + 'expiry' => 'Це посилання діє 60 хвилин. Якщо ви не надсилали запит, просто проігноруйте цей лист.', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :count допис під загрозою в :workspace|[2,4] :count дописи під загрозою в :workspace|[0,*] :count дописів під загрозою в :workspace', + 'title' => 'Дописи можуть не опублікуватися', + 'heading' => 'Дописи можуть не опублікуватися', + 'intro' => 'Наведені облікові записи в робочому просторі :workspace потрібно підключити повторно, щоб ці заплановані дописи опублікувалися:', + 'posts_label' => '{1} :count запланований допис: :times UTC|[2,4] :count заплановані дописи: :times UTC|[0,*] :count запланованих дописів: :times UTC', + 'reconnect_cta' => 'Підключіть ці облікові записи зараз, щоб не пропустити заплановані дописи.', + 'button' => 'Підключити повторно', + ], + + 'post_publish_failed' => [ + 'subject' => 'Не вдалося опублікувати допис у :workspace', + 'title' => 'Не вдалося опублікувати допис', + 'preview' => 'Одна або кілька платформ не змогли опублікувати допис.', + 'heading' => 'Не вдалося опублікувати допис', + 'body' => 'Ваш запланований допис у робочому просторі :workspace не опублікувався на одній або кількох платформах.', + 'platforms_title' => 'Платформи з помилкою:', + 'button' => 'Переглянути допис', + ], + + 'post_published' => [ + 'subject' => 'Ваш допис опубліковано в :workspace', + 'title' => 'Ваш допис опубліковано', + 'preview' => 'Ваш допис успішно опубліковано.', + 'heading' => 'Ваш допис опубліковано', + 'body' => 'Ваш допис у робочому просторі :workspace успішно опубліковано.', + 'platforms_title' => 'Опубліковано в:', + 'view_post' => 'Переглянути допис', + 'button' => 'Переглянути допис', + ], + + 'webhook_paused' => [ + 'subject' => 'Вебхук призупинено: :endpoint', + 'title' => 'Вебхук призупинено після повторних помилок', + 'preview' => 'Ми призупинили вебхук після 5 підряд невдалих доставок.', + 'heading' => 'Вебхук призупинено після повторних помилок', + 'body' => 'Ми призупинили вебхук на :endpoint після 5 підряд невдалих доставок. Перевірте endpoint і ввімкніть його знову на сторінці вебхука.', + 'button' => 'Відкрити вебхук', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :count акаунт потрібно перепідключити в :workspace|[2,*] :count акаунтів потрібно перепідключити в :workspace', + 'subject' => '{1} :count акаунт потрібно перепідключити в :workspace|[0,*] :count акаунтів потрібно перепідключити в :workspace', 'title' => 'Акаунти потребують перепідключення', + 'heading' => 'Акаунти потребують перепідключення', 'intro' => 'Наступні соціальні акаунти у вашому робочому просторі :workspace було від’єднано, і їх потрібно перепідключити:', 'reasons_title' => 'Це могло статися через:', 'reason_expired' => 'Термін дії токенів доступу минув', @@ -19,4 +102,16 @@ 'reconnect_cta' => 'Перепідключіть ці акаунти, щоб продовжити планування та публікацію постів.', 'button' => 'Перепідключити акаунти', ], + + 'workspace_invite' => [ + 'subject' => 'Вас запросили приєднатися до :account', + 'title' => 'Вас запросили приєднатися до :account', + 'preview' => 'Вас запросили приєднатися до :account', + 'heading' => 'Вас запрошено!', + 'intro' => 'Вас запросили до співпраці в робочому просторі :account.', + 'role' => 'Вас запросили як :role.', + 'button' => 'Прийняти запрошення', + 'expiry' => 'Це запрошення діє 7 днів.', + ], + ]; diff --git a/lang/uk/webhooks.php b/lang/uk/webhooks.php index 3d6acfce7..6b6aa86c4 100644 --- a/lang/uk/webhooks.php +++ b/lang/uk/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => 'Подію вебхука надіслано знову.', 'tested' => 'Тестову подію надіслано.', ], - 'mail' => [ - 'paused_subject' => 'Вебхук призупинено: :endpoint', - 'paused_title' => 'Вебхук призупинено після повторних помилок', - 'paused_preview' => 'Ми призупинили вебхук після 5 підряд невдалих доставок.', - 'paused_body' => 'Ми призупинили вебхук на :endpoint після 5 підряд невдалих доставок. Перевірте endpoint і ввімкніть його знову на сторінці вебхука.', - 'paused_cta' => 'Відкрити вебхук', - ], ]; diff --git a/lang/zh/mail.php b/lang/zh/mail.php index 6a624567b..1644fb463 100644 --- a/lang/zh/mail.php +++ b/lang/zh/mail.php @@ -1,16 +1,99 @@ [ + + 'layout' => [ + 'tagline' => '开源的社交媒体排期发布工具', + 'manage_notifications' => '管理通知', + 'signoff' => '祝好,', + 'team' => 'TryPost 团队', + ], + + 'account_disconnected' => [ + 'subject' => ':workspace 中的 :platform 账号需要重新连接', + 'title' => ':platform 账号需要重新连接', + 'preview' => '请重新连接 :workspace 中的 :platform 账号,以继续排期发布。', + 'heading' => '账号已断开', + 'intro' => '你的 :platform 账号 :account 已从工作区 :workspace 断开。', + 'reasons_title' => '可能的原因:', + 'reason_expired' => '访问令牌已过期', + 'reason_revoked' => '你撤销了 TryPost 的访问权限', + 'reason_error' => '出现了认证错误', + 'reconnect_cta' => '请重新连接账号,以继续排期和发布内容。', + 'button' => '重新连接账号', + ], + + 'email_verification' => [ + 'subject' => '请验证你的邮箱地址', + 'preview' => '请验证你的邮箱地址。', + 'greeting' => ':name,你好:', + 'body' => '请点击下方按钮确认你的邮箱地址:', + 'button' => '验证邮箱', + 'ignore' => '如果你没有创建账号,可以忽略这封邮件。', + ], + + 'mentioned_in_comment' => [ 'subject' => ':name 在 TryPost 上提到了你', 'title' => ':name 提到了你', 'intro' => ':name 在一条帖子评论中提到了你。', - 'cta' => '查看评论', + 'button' => '查看评论', + ], + + 'password_reset' => [ + 'subject' => '重置你的密码', + 'preview' => '重置你的密码。', + 'greeting' => ':name,你好:', + 'body' => '我们收到了重置密码的请求。请点击下方按钮设置新密码:', + 'button' => '重置密码', + 'expiry' => '该链接 60 分钟后失效。如果不是你本人操作,可以忽略这封邮件。', + ], + + 'post_at_risk' => [ + 'subject' => '{1} :workspace 中有 :count 条内容可能无法发布|[0,*] :workspace 中有 :count 条内容可能无法发布', + 'title' => '内容可能无法发布', + 'heading' => '内容可能无法发布', + 'intro' => '需要重新连接工作区 :workspace 中的以下账号,这些已排期的内容才能发布:', + 'posts_label' => '{1} 已排期 :count 条::times UTC|[0,*] 已排期 :count 条::times UTC', + 'reconnect_cta' => '请立即重新连接这些账号,以免错过已排期的发布。', + 'button' => '重新连接账号', + ], + + 'post_publish_failed' => [ + 'subject' => ':workspace 中的内容发布失败', + 'title' => '内容发布失败', + 'preview' => '有一个或多个平台发布失败。', + 'heading' => '内容发布失败', + 'body' => '工作区 :workspace 中已排期的内容在一个或多个平台上发布失败。', + 'platforms_title' => '发布失败的平台:', + 'button' => '查看内容', + ], + + 'post_published' => [ + 'subject' => ':workspace 中的内容已发布', + 'title' => '内容已发布', + 'preview' => '你的内容已成功发布。', + 'heading' => '内容已发布', + 'body' => '工作区 :workspace 中的内容已成功发布。', + 'platforms_title' => '已发布到:', + 'view_post' => '查看内容', + 'button' => '查看内容', + ], + + 'webhook_paused' => [ + 'subject' => 'Webhook 已暂停::endpoint', + 'title' => 'Webhook 因连续失败已暂停', + 'preview' => '连续 5 次投递失败后,我们暂停了一个 webhook。', + 'heading' => 'Webhook 因连续失败已暂停', + 'body' => '连续 5 次投递失败后,我们暂停了 :endpoint 上的 webhook。请检查该 endpoint,并在 webhook 详情页重新启用。', + 'button' => '查看 webhook', ], 'workspace_connections_disconnected' => [ - 'subject' => '{1} :workspace 工作区中有 :count 个账号需要重新连接|[2,*] :workspace 工作区中有 :count 个账号需要重新连接', + 'subject' => '{1} :workspace 工作区中有 :count 个账号需要重新连接|[0,*] :workspace 工作区中有 :count 个账号需要重新连接', 'title' => '账号需要重新连接', + 'heading' => '账号需要重新连接', 'intro' => '你 :workspace 工作区中的以下社交账号已断开连接,需要重新连接:', 'reasons_title' => '这可能是由于以下原因:', 'reason_expired' => '访问令牌已过期', @@ -19,4 +102,16 @@ 'reconnect_cta' => '请重新连接这些账号,以继续安排和发布帖子。', 'button' => '重新连接账号', ], + + 'workspace_invite' => [ + 'subject' => '你被邀请加入 :account', + 'title' => '你被邀请加入 :account', + 'preview' => '你被邀请加入 :account', + 'heading' => '你收到一份邀请', + 'intro' => '你被邀请加入工作区 :account 一起协作。', + 'role' => '你被邀请的角色是 :role。', + 'button' => '接受邀请', + 'expiry' => '该邀请 7 天后失效。', + ], + ]; diff --git a/lang/zh/webhooks.php b/lang/zh/webhooks.php index d9e6cd3cf..0ed73ce8c 100644 --- a/lang/zh/webhooks.php +++ b/lang/zh/webhooks.php @@ -129,11 +129,4 @@ 'replayed' => '已重新发送 webhook 事件。', 'tested' => '已发送测试事件。', ], - 'mail' => [ - 'paused_subject' => 'Webhook 已暂停::endpoint', - 'paused_title' => 'Webhook 因连续失败已暂停', - 'paused_preview' => '连续 5 次投递失败后,我们暂停了一个 webhook。', - 'paused_body' => '连续 5 次投递失败后,我们暂停了 :endpoint 上的 webhook。请检查该 endpoint,并在 webhook 详情页重新启用。', - 'paused_cta' => '查看 webhook', - ], ]; diff --git a/maizzle/components/button.html b/maizzle/components/button.html index 379219f6e..22336c72f 100644 --- a/maizzle/components/button.html +++ b/maizzle/components/button.html @@ -20,7 +20,7 @@ if (props['bg-color'] && !hasBgClass()) { styles.push(`background-color: ${props['bg-color']};`) } else { - styles.push('background-color: #262626;') + styles.push('background-color: #7c3aed;') } if (props.color) { diff --git a/maizzle/components/footer-authenticated.html b/maizzle/components/footer-authenticated.html index 66dba8687..b9d8a00fa 100644 --- a/maizzle/components/footer-authenticated.html +++ b/maizzle/components/footer-authenticated.html @@ -1,13 +1,13 @@

- Open-source social media scheduling tool + @{{ __('mail.layout.tagline') }}

- Manage notifications + @{{ __('mail.layout.manage_notifications') }}

@@ -34,7 +34,7 @@ - + Instagram diff --git a/maizzle/components/footer.html b/maizzle/components/footer.html index 0feba36c5..6fe68efd0 100644 --- a/maizzle/components/footer.html +++ b/maizzle/components/footer.html @@ -1,7 +1,7 @@

- Open-source social media scheduling tool + @{{ __('mail.layout.tagline') }}

@@ -27,7 +27,7 @@ diff --git a/maizzle/templates/account-disconnected.html b/maizzle/templates/account-disconnected.html index b572c9be7..24a9b44e2 100644 --- a/maizzle/templates/account-disconnected.html +++ b/maizzle/templates/account-disconnected.html @@ -9,32 +9,32 @@ diff --git a/maizzle/templates/email-verification.html b/maizzle/templates/email-verification.html index fe7a23440..874db682d 100644 --- a/maizzle/templates/email-verification.html +++ b/maizzle/templates/email-verification.html @@ -8,30 +8,30 @@ diff --git a/maizzle/templates/mentioned-in-comment.html b/maizzle/templates/mentioned-in-comment.html index 99b705656..6a0f76123 100644 --- a/maizzle/templates/mentioned-in-comment.html +++ b/maizzle/templates/mentioned-in-comment.html @@ -13,7 +13,7 @@

- {!! __('mail.mentioned.intro', ['name' => ''.e($authorName).'']) !!} + {!! __('mail.mentioned_in_comment.intro', ['name' => ''.e($authorName).'']) !!}

@@ -24,7 +24,7 @@

- @{{ __('mail.mentioned.cta') }} → + @{{ __('mail.mentioned_in_comment.button') }}
diff --git a/maizzle/templates/password-reset.html b/maizzle/templates/password-reset.html index a0b3b0a8d..e1cdd7544 100644 --- a/maizzle/templates/password-reset.html +++ b/maizzle/templates/password-reset.html @@ -8,30 +8,30 @@

diff --git a/maizzle/templates/post-at-risk.html b/maizzle/templates/post-at-risk.html index 1fa088ca0..7155b4489 100644 --- a/maizzle/templates/post-at-risk.html +++ b/maizzle/templates/post-at-risk.html @@ -9,11 +9,11 @@ diff --git a/maizzle/templates/post-publish-failed.html b/maizzle/templates/post-publish-failed.html index b0f986bac..084d26cc4 100644 --- a/maizzle/templates/post-publish-failed.html +++ b/maizzle/templates/post-publish-failed.html @@ -9,16 +9,16 @@ diff --git a/maizzle/templates/post-published.html b/maizzle/templates/post-published.html index bac501173..2260f0b52 100644 --- a/maizzle/templates/post-published.html +++ b/maizzle/templates/post-published.html @@ -9,23 +9,23 @@ diff --git a/maizzle/templates/webhook-paused.html b/maizzle/templates/webhook-paused.html index e190680d3..817bfe4d6 100644 --- a/maizzle/templates/webhook-paused.html +++ b/maizzle/templates/webhook-paused.html @@ -9,18 +9,18 @@ diff --git a/maizzle/templates/workspace-connections-disconnected.html b/maizzle/templates/workspace-connections-disconnected.html index d9de6a73a..64da447f4 100644 --- a/maizzle/templates/workspace-connections-disconnected.html +++ b/maizzle/templates/workspace-connections-disconnected.html @@ -9,11 +9,11 @@ diff --git a/maizzle/templates/workspace-invite.html b/maizzle/templates/workspace-invite.html index 6d1c8b78e..52863f4ee 100644 --- a/maizzle/templates/workspace-invite.html +++ b/maizzle/templates/workspace-invite.html @@ -9,29 +9,29 @@ diff --git a/public/images/flags/BR.svg b/public/images/flags/BR.svg new file mode 100644 index 000000000..f4dbb020a --- /dev/null +++ b/public/images/flags/BR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/CN.svg b/public/images/flags/CN.svg new file mode 100644 index 000000000..64629b25e --- /dev/null +++ b/public/images/flags/CN.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/DE.svg b/public/images/flags/DE.svg new file mode 100644 index 000000000..295d13e1b --- /dev/null +++ b/public/images/flags/DE.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/ES.svg b/public/images/flags/ES.svg new file mode 100644 index 000000000..e14332494 --- /dev/null +++ b/public/images/flags/ES.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/FR.svg b/public/images/flags/FR.svg new file mode 100644 index 000000000..ec311d791 --- /dev/null +++ b/public/images/flags/FR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/GR.svg b/public/images/flags/GR.svg new file mode 100644 index 000000000..00671707e --- /dev/null +++ b/public/images/flags/GR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/IT.svg b/public/images/flags/IT.svg new file mode 100644 index 000000000..2eb128985 --- /dev/null +++ b/public/images/flags/IT.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/JP.svg b/public/images/flags/JP.svg new file mode 100644 index 000000000..87145142b --- /dev/null +++ b/public/images/flags/JP.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/KR.svg b/public/images/flags/KR.svg new file mode 100644 index 000000000..e86b88892 --- /dev/null +++ b/public/images/flags/KR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/NL.svg b/public/images/flags/NL.svg new file mode 100644 index 000000000..3c762c3e9 --- /dev/null +++ b/public/images/flags/NL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/PL.svg b/public/images/flags/PL.svg new file mode 100644 index 000000000..e98271f31 --- /dev/null +++ b/public/images/flags/PL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/RU.svg b/public/images/flags/RU.svg new file mode 100644 index 000000000..5e78ce862 --- /dev/null +++ b/public/images/flags/RU.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/SA.svg b/public/images/flags/SA.svg new file mode 100644 index 000000000..9632754f2 --- /dev/null +++ b/public/images/flags/SA.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/TR.svg b/public/images/flags/TR.svg new file mode 100644 index 000000000..1e08e2357 --- /dev/null +++ b/public/images/flags/TR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/UA.svg b/public/images/flags/UA.svg new file mode 100644 index 000000000..8eaa507ee --- /dev/null +++ b/public/images/flags/UA.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/flags/US.svg b/public/images/flags/US.svg new file mode 100644 index 000000000..dc427e711 --- /dev/null +++ b/public/images/flags/US.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/app.ts b/resources/js/app.ts index 6bdb68d69..9c4792cd6 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -8,7 +8,7 @@ import type { DefineComponent } from 'vue'; import { createApp, h } from 'vue'; import { initializeDataLayer } from './datalayer'; -import dayjs from './dayjs'; +import { bootLocale, i18nConfig, syncLocale } from './language'; import { syncContentTypeMediaRules } from './lib/contentTypeMediaRules'; import { capturePageview, initializePostHog, syncPostHogContext } from './posthog'; import type { Auth } from './types'; @@ -23,11 +23,7 @@ createInertiaApp({ import.meta.glob('./pages/**/*.vue'), ), setup({ el, App, props, plugin }) { - // Get locale from shared Inertia props - const locale = (props.initialPage.props as { locale?: string })?.locale || 'en'; - - // Set dayjs locale based on user's language - dayjs.locale(locale.toLowerCase()); + const locale = bootLocale(props.initialPage.props); const auth = props.initialPage.props.auth as Auth | undefined; const flash = props.initialPage.props.flash as @@ -50,6 +46,13 @@ createInertiaApp({ syncContentTypeMediaRules(props.initialPage); capturePageview(); + // `success`, not `navigate`: switching language answers with `back()`, + // which keeps the same URL, so Inertia updates the props without + // reporting a navigation. + router.on('success', (event) => { + syncLocale(event.detail.page.props); + }); + router.on('navigate', (event) => { syncPostHogContext(event.detail.page); syncContentTypeMediaRules(event.detail.page); @@ -57,13 +60,7 @@ createInertiaApp({ }); createApp({ render: () => h(App, props) }) - .use(i18nVue, { - lang: locale, - resolve: async (lang: string) => { - const langs = import.meta.glob('../../lang/*.json'); - return await langs[`../../lang/php_${lang}.json`](); - }, - }) + .use(i18nVue, i18nConfig(locale)) .use(plugin) .mount(el); }, diff --git a/resources/js/components/NavMain.vue b/resources/js/components/NavMain.vue index ece3b176e..64df69baa 100644 --- a/resources/js/components/NavMain.vue +++ b/resources/js/components/NavMain.vue @@ -32,7 +32,7 @@ const { urlIsActive } = useActiveUrl(); :is-active="urlIsActive(item.activePattern ?? item.href, { exact: item.exact, exclude: item.excludeActive })" :tooltip="item.title" > - + {{ item.title }} diff --git a/resources/js/components/WorkspaceMenuContent.vue b/resources/js/components/WorkspaceMenuContent.vue index 7ff8eaa5f..12c802e3a 100644 --- a/resources/js/components/WorkspaceMenuContent.vue +++ b/resources/js/components/WorkspaceMenuContent.vue @@ -33,7 +33,7 @@ import { create as createWorkspaceRoute, switchMethod, } from '@/routes/app/workspaces'; -import type { User } from '@/types'; +import type { Language, User } from '@/types'; interface Workspace { id: string; @@ -41,11 +41,6 @@ interface Workspace { logo_url: string | null; } -interface Language { - code: string; - name: string; -} - const props = defineProps<{ user: User; currentWorkspace: Workspace | null; @@ -68,13 +63,7 @@ const currentLanguage = computed(() => ); const switchLanguage = (code: string): void => { - router.put( - updateLanguage.url(), - { locale: code }, - { - onSuccess: () => window.location.reload(), - }, - ); + router.put(updateLanguage.url(), { locale: code }); }; const switchWorkspace = (workspaceId: string): void => { @@ -149,8 +138,14 @@ const handleLogout = (): void => { - - + + + {{ $t('sidebar.language', { name: currentLanguage?.name ?? 'English', @@ -167,8 +162,14 @@ const handleLogout = (): void => { ? 'bg-accent' : '' " + :data-testid="`sidebar-language-${language.code}`" @click="switchLanguage(language.code)" > + {{ language.name }} +import { IconCheck, IconChevronDown } from '@tabler/icons-vue'; +import { computed } from 'vue'; + +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { useGuestLocale } from '@/composables/useGuestLocale'; + +const { locale, languages } = useGuestLocale(); + +const current = computed(() => + languages.value.find((language) => language.code === locale.value), +); + + + diff --git a/resources/js/components/ui/sidebar/Sidebar.vue b/resources/js/components/ui/sidebar/Sidebar.vue index d33d8c625..7f6b7a432 100644 --- a/resources/js/components/ui/sidebar/Sidebar.vue +++ b/resources/js/components/ui/sidebar/Sidebar.vue @@ -73,14 +73,14 @@ const { isMobile, state, openMobile, setOpenMobile } = useSidebar() />
{ + const page = usePage(); + + const languages = computed( + () => page.props.languages as Language[], + ); + + const locale = computed({ + get: () => guestLocale() ?? (page.props.locale as string), + set: (value) => { + const language = languages.value.find( + (candidate) => candidate.code === value, + ); + + if (language) { + chooseGuestLocale(language); + } + }, + }); + + return { locale, chosen: computed(guestLocale), languages }; +}; diff --git a/resources/js/date.ts b/resources/js/date.ts index ba72480e0..41f6f127e 100644 --- a/resources/js/date.ts +++ b/resources/js/date.ts @@ -1,4 +1,12 @@ import dayjs from '@/dayjs'; +import { activeLocale } from '@/language'; + +/** + * A dayjs instance bound to the current language. `dayjs.locale()` is global and + * not reactive, so reading the ref here is what makes a computed re-run when the + * language changes instead of serving the previous one from cache. + */ +const localized = (value?: dayjs.ConfigType) => dayjs(value).locale(activeLocale.value.toLowerCase()); /** * Obtém o timezone do usuário @@ -11,13 +19,13 @@ function getUserTimezone(): string { /** Resolve scheduled local datetime for platform previews, else now. */ const resolvePreviewPostedAt = (postedAt?: string | null) => { if (postedAt) { - const parsed = dayjs(postedAt); + const parsed = localized(postedAt); if (parsed.isValid()) { return parsed; } } - return dayjs(); + return localized(); }; /** X / Bluesky style: `4:21 PM · Aug 5, 2026` (locale-aware). */ diff --git a/resources/js/language.ts b/resources/js/language.ts new file mode 100644 index 000000000..178a862f9 --- /dev/null +++ b/resources/js/language.ts @@ -0,0 +1,100 @@ +import { getActiveLanguage, loadLanguageAsync } from 'laravel-vue-i18n'; +import { ref } from 'vue'; + +import dayjs from './dayjs'; +import type { Auth, Language } from './types'; + +interface LocaleProps { + locale?: string; + auth?: Auth; + languages?: Language[]; +} + +const chosen = ref(null); + +/** + * The locale the page is currently rendered in. `dayjs.locale()` is global and + * not reactive, so anything formatting a date has to depend on this instead — + * otherwise a cached computed keeps the previous language's month and day names. + */ +export const activeLocale = ref('en'); + +export const guestLocale = (): string | null => chosen.value; + +/** + * Align everything outside the translation strings themselves. + */ +const align = (locale: string, direction?: string): void => { + dayjs.locale(locale.toLowerCase()); + activeLocale.value = locale; + document.documentElement.lang = locale; + + if (direction) { + document.documentElement.dir = direction; + } +}; + +/** + * Only Arabic is right to left, but the rule lives in the Locale enum and + * reaches the client in the shared languages prop. + */ +const directionOf = ( + locale: string, + languages?: Language[], +): string | undefined => + languages?.find((language) => language.code === locale)?.dir; + +/** + * The locale the app starts in. The i18n plugin loads the strings itself from + * the value returned here, so this only aligns dayjs and the document. + */ +export const bootLocale = (props: LocaleProps): string => { + const locale = props.locale ?? 'en'; + + align(locale); + + return locale; +}; + +/** + * Record a logged-out visitor's pick and apply it. It has to be remembered: the + * server renders every guest page in the default, so nothing else would keep the + * choice across an Inertia visit between the auth screens. + */ +export const chooseGuestLocale = (language: Language): void => { + chosen.value = language.code; + + void loadLanguageAsync(language.code); + align(language.code, language.dir); +}; + +/** + * Re-align after an Inertia visit. The locale is read once at boot but changes + * mid-session — logging in swaps the guest default for the account's language + * over a visit that never re-runs the app setup. + */ +export const syncLocale = (props: LocaleProps): void => { + const authenticated = Boolean(props.auth?.user); + + if (authenticated) { + chosen.value = null; + } + + const next = authenticated ? props.locale : (chosen.value ?? props.locale); + + if (!next || next === getActiveLanguage()) { + return; + } + + void loadLanguageAsync(next); + align(next, directionOf(next, props.languages)); +}; + +export const i18nConfig = (lang: string) => ({ + lang, + resolve: async (locale: string) => { + const langs = import.meta.glob('../../lang/*.json'); + + return await langs[`../../lang/php_${locale}.json`](); + }, +}); diff --git a/resources/js/layouts/auth/AuthSplitLayout.vue b/resources/js/layouts/auth/AuthSplitLayout.vue index 1450ca41f..362dd62e1 100644 --- a/resources/js/layouts/auth/AuthSplitLayout.vue +++ b/resources/js/layouts/auth/AuthSplitLayout.vue @@ -1,4 +1,5 @@