diff --git a/.env.e2e.example b/.env.e2e.example index aef42c5..e4d5aa4 100644 --- a/.env.e2e.example +++ b/.env.e2e.example @@ -54,6 +54,11 @@ GITHUB_OAUTH_TOKEN_URL= GITHUB_OAUTH_USER_URL= GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS= GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS= +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= +GOOGLE_OAUTH_BASE_URL= +GOOGLE_OAUTH_HTTP_CONNECT_TIMEOUT_MS= +GOOGLE_OAUTH_HTTP_RECEIVE_TIMEOUT_MS= E2E_BASE_URL=https://proxy E2E_MAILPIT_URL=http://mailpit:8025 diff --git a/.env.example b/.env.example index 8e2df3b..671e506 100644 --- a/.env.example +++ b/.env.example @@ -52,6 +52,17 @@ GITHUB_OAUTH_USER_URL= GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS= GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS= +# Optional Google OpenID Connect registration and sign-in. Leave both empty +# until a Google OAuth Web client exists. Its callback URL must be: +# https://YOUR_PHX_HOST/auth/google/callback +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= +# Leave endpoint and timeout overrides empty for Google's discovery endpoint +# and Req defaults. The base URL override exists for isolated protocol tests. +GOOGLE_OAUTH_BASE_URL= +GOOGLE_OAUTH_HTTP_CONNECT_TIMEOUT_MS= +GOOGLE_OAUTH_HTTP_RECEIVE_TIMEOUT_MS= + # Optional provider-neutral HTTP push boundary. Leave both endpoint and token # empty to disable product push jobs. When enabled, all four numeric values are # required deployment inputs; the project does not claim universal production diff --git a/.env.load.example b/.env.load.example index 6c23372..a42e246 100644 --- a/.env.load.example +++ b/.env.load.example @@ -55,6 +55,11 @@ GITHUB_OAUTH_TOKEN_URL= GITHUB_OAUTH_USER_URL= GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS= GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS= +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= +GOOGLE_OAUTH_BASE_URL= +GOOGLE_OAUTH_HTTP_CONNECT_TIMEOUT_MS= +GOOGLE_OAUTH_HTTP_RECEIVE_TIMEOUT_MS= # These are reproducible experiment inputs, not capacity requirements, # production traffic forecasts, alert thresholds, or recommended limits. diff --git a/README.md b/README.md index 6bb82ab..f2612c3 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ thank-you link; money goes directly between users outside the platform. ## What is implemented -- Phoenix 1.8 LiveView application with email magic-link/password auth and 18+ - self-attestation. +- Phoenix 1.8 LiveView application with passwordless email magic links, + optional passwords, optional Google OpenID Connect registration/sign-in, and + 18+ self-attestation. - Data-driven, translated category tree with validated per-category fields, seven selectable urgent/roadside scenarios, community proposals/votes, and human approve/reject/merge tools. @@ -140,7 +141,7 @@ restores it into a new temporary database, checks corruption and interruption failure paths, removes those temporary buckets, and retains the successful encrypted bucket in local MinIO. It never writes a plaintext dump to the host. -Exercise the real OAuth/SMTP protocol clients and the provider-neutral push +Exercise the real GitHub OAuth, Google OIDC, SMTP protocol clients, and the provider-neutral push boundary entirely inside an isolated Docker network: ```bash @@ -178,8 +179,9 @@ printing them, writes mode `0600`, and refuses to replace an existing file: ``` Configure the verified reverse-proxy source IP/CIDR and transactional SMTP -provider in that file, then validate its structure and the production Compose -render: +provider in that file. Email registration and magic-link login are unusable for +real recipients until that relay and its accepted sender are configured. +Then validate the file structure and the production Compose render: ```bash ./scripts/validate-production-env.sh .env.production whoneedhelp.com @@ -207,6 +209,39 @@ docker compose up -d --wait This preserves the named PostgreSQL volume, but invalidates existing browser sessions and changes handover codes for active local requests. +To rotate only the local PostgreSQL role and matching `DATABASE_URL`, without +changing Phoenix sessions or handover codes, use: + +```bash +./scripts/rotate-local-secrets.sh --database-only +docker compose up -d --wait +``` + +### Optional Google registration and sign-in + +Create a Google OAuth 2.0 Web application only for an owned HTTPS origin. Add +this exact authorized redirect URI in Google Cloud: + +```text +https://YOUR_PHX_HOST/auth/google/callback +``` + +Set both `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` in the +ignored `.env` or deployment Secret, then recreate the application containers. +If both values are empty, the Google buttons remain visible but disabled with +an explanation. A partial pair is rejected at startup and by the production +environment validator. + +The flow requests `openid email profile`, verifies the provider email claim, +uses state, nonce, and PKCE, and discards provider tokens. Google registration +creates a confirmed local account only after the user accepts the 18+ safety +terms. Google login works only for an identity already linked to that local +account. An existing local account is never merged merely because Google +returns the same email; sign in by email or password and connect Google from +the sudo-protected account settings page instead. Leave +`GOOGLE_OAUTH_BASE_URL` and the Google HTTP timeout variables empty outside the +isolated protocol drill. + ### Optional verified GitHub linking Create a GitHub OAuth App with this exact callback URL for the active public diff --git a/compose.external-boundaries.yaml b/compose.external-boundaries.yaml index e74a555..81d1056 100644 --- a/compose.external-boundaries.yaml +++ b/compose.external-boundaries.yaml @@ -17,6 +17,11 @@ x-boundary-app-environment: &boundary-app-environment ALLOW_INSECURE_EXTERNAL_HTTP: "true" GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS: "100" GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS: "100" + GOOGLE_OAUTH_CLIENT_ID: ${EXTERNAL_OAUTH_CLIENT_ID:?Set EXTERNAL_OAUTH_CLIENT_ID} + GOOGLE_OAUTH_CLIENT_SECRET: ${EXTERNAL_OAUTH_CLIENT_SECRET:?Set EXTERNAL_OAUTH_CLIENT_SECRET} + GOOGLE_OAUTH_BASE_URL: http://external-mock:8080 + GOOGLE_OAUTH_HTTP_CONNECT_TIMEOUT_MS: "100" + GOOGLE_OAUTH_HTTP_RECEIVE_TIMEOUT_MS: "100" SMTP_RELAY: external-mock SMTP_PORT: "2525" SMTP_USERNAME: "" diff --git a/compose.yaml b/compose.yaml index 794b962..b9750de 100644 --- a/compose.yaml +++ b/compose.yaml @@ -37,6 +37,11 @@ x-app-environment: &app-environment GITHUB_OAUTH_USER_URL: ${GITHUB_OAUTH_USER_URL:-} GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS: ${GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS:-} GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS: ${GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS:-} + GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_OAUTH_CLIENT_ID:-} + GOOGLE_OAUTH_CLIENT_SECRET: ${GOOGLE_OAUTH_CLIENT_SECRET:-} + GOOGLE_OAUTH_BASE_URL: ${GOOGLE_OAUTH_BASE_URL:-} + GOOGLE_OAUTH_HTTP_CONNECT_TIMEOUT_MS: ${GOOGLE_OAUTH_HTTP_CONNECT_TIMEOUT_MS:-} + GOOGLE_OAUTH_HTTP_RECEIVE_TIMEOUT_MS: ${GOOGLE_OAUTH_HTTP_RECEIVE_TIMEOUT_MS:-} PUSH_HTTP_ENDPOINT: ${PUSH_HTTP_ENDPOINT:-} PUSH_HTTP_BEARER_TOKEN: ${PUSH_HTTP_BEARER_TOKEN:-} PUSH_HTTP_MAX_ATTEMPTS: ${PUSH_HTTP_MAX_ATTEMPTS:-} diff --git a/config/runtime.exs b/config/runtime.exs index cd2aa31..5be771e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -133,20 +133,21 @@ oauth_endpoint = fn name, default -> end end -oauth_http_options = +oauth_http_options = fn prefix -> [retry: false] |> then(fn options -> - case optional_positive_integer.("GITHUB_OAUTH_HTTP_RECEIVE_TIMEOUT_MS") do + case optional_positive_integer.("#{prefix}_OAUTH_HTTP_RECEIVE_TIMEOUT_MS") do nil -> options timeout -> Keyword.put(options, :receive_timeout, timeout) end end) |> then(fn options -> - case optional_positive_integer.("GITHUB_OAUTH_HTTP_CONNECT_TIMEOUT_MS") do + case optional_positive_integer.("#{prefix}_OAUTH_HTTP_CONNECT_TIMEOUT_MS") do nil -> options timeout -> Keyword.put(options, :connect_options, timeout: timeout) end end) +end github_oauth = case { @@ -172,7 +173,7 @@ github_oauth = "https://github.com/login/oauth/access_token" ), user_url: oauth_endpoint.("GITHUB_OAUTH_USER_URL", "https://api.github.com/user"), - http_adapter: {Assent.HTTPAdapter.Req, oauth_http_options} + http_adapter: {Assent.HTTPAdapter.Req, oauth_http_options.("GITHUB")} ] } @@ -187,6 +188,33 @@ github_oauth = config :who_need_help, :social_oauth, github_oauth +google_auth = + case { + System.get_env("GOOGLE_OAUTH_CLIENT_ID"), + System.get_env("GOOGLE_OAUTH_CLIENT_SECRET") + } do + {client_id, client_secret} + when is_binary(client_id) and client_id != "" and is_binary(client_secret) and + client_secret != "" -> + [ + client_id: client_id, + client_secret: client_secret, + base_url: oauth_endpoint.("GOOGLE_OAUTH_BASE_URL", "https://accounts.google.com/"), + authorization_params: [scope: "email profile"], + http_adapter: {Assent.HTTPAdapter.Req, oauth_http_options.("GOOGLE")} + ] + + {client_id, client_secret} when client_id in [nil, ""] and client_secret in [nil, ""] -> + [] + + _partial_configuration -> + raise """ + GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET must either both be set or both be empty. + """ + end + +config :who_need_help, :google_auth, google_auth + push_configuration = case { System.get_env("PUSH_HTTP_ENDPOINT"), @@ -366,6 +394,23 @@ if config_env() == :prod do other -> raise "SMTP_SSL must be true, false, 1, or 0; got #{inspect(other)}" end + smtp_username = System.get_env("SMTP_USERNAME") + smtp_password = System.get_env("SMTP_PASSWORD") + smtp_username_set? = is_binary(smtp_username) and smtp_username != "" + smtp_password_set? = is_binary(smtp_password) and smtp_password != "" + + if smtp_username_set? != smtp_password_set? do + raise "SMTP_USERNAME and SMTP_PASSWORD must either both be set or both be empty." + end + + if smtp_auth == :always and not (smtp_username_set? and smtp_password_set?) do + raise "SMTP_USERNAME and SMTP_PASSWORD are required when SMTP_AUTH is always." + end + + if smtp_ssl and smtp_tls != :never do + raise "SMTP_TLS must be never when SMTP_SSL enables an implicit TLS connection." + end + smtp_config = [ adapter: Swoosh.Adapters.SMTP, @@ -376,13 +421,13 @@ if config_env() == :prod do ssl: smtp_ssl ] |> then(fn config -> - case System.get_env("SMTP_USERNAME") do + case smtp_username do value when is_binary(value) and value != "" -> Keyword.put(config, :username, value) _ -> config end end) |> then(fn config -> - case System.get_env("SMTP_PASSWORD") do + case smtp_password do value when is_binary(value) and value != "" -> Keyword.put(config, :password, value) _ -> config end diff --git a/config/test.exs b/config/test.exs index b4d3cae..8046bd8 100644 --- a/config/test.exs +++ b/config/test.exs @@ -29,6 +29,7 @@ config :who_need_help, WhoNeedHelpWeb.Endpoint, # In test we don't send emails config :who_need_help, WhoNeedHelp.Mailer, adapter: Swoosh.Adapters.Test config :who_need_help, :social_oauth_adapter, WhoNeedHelp.SocialOAuthFake +config :who_need_help, :google_auth_adapter, WhoNeedHelp.GoogleAuthFake config :who_need_help, :metrics_token, "test-metrics-token" # Disable swoosh api client as it is only required for production adapters diff --git a/deploy/helm/who-need-help/values.yaml b/deploy/helm/who-need-help/values.yaml index 97e89ad..6816a8f 100644 --- a/deploy/helm/who-need-help/values.yaml +++ b/deploy/helm/who-need-help/values.yaml @@ -39,7 +39,9 @@ app: # Required. The Secret must contain DATABASE_URL, SECRET_KEY_BASE, # HANDOVER_SECRET, RELEASE_COOKIE, and METRICS_TOKEN. It may also contain both # GITHUB_OAUTH_CLIENT_ID and GITHUB_OAUTH_CLIENT_SECRET to enable verified -# GitHub linking. Push is opt-in: provide PUSH_HTTP_ENDPOINT, +# GitHub linking, and both GOOGLE_OAUTH_CLIENT_ID and +# GOOGLE_OAUTH_CLIENT_SECRET to enable Google registration/sign-in. Push is +# opt-in: provide PUSH_HTTP_ENDPOINT, # PUSH_HTTP_BEARER_TOKEN, PUSH_HTTP_MAX_ATTEMPTS, # PUSH_HTTP_RECEIVE_TIMEOUT_MS, PUSH_HTTP_CONNECT_TIMEOUT_MS, and # PUSH_HTTP_RETRY_DELAY_MS as one complete set. diff --git a/docs/architecture.md b/docs/architecture.md index 28f4708..852f49e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -47,8 +47,13 @@ and are not represented as complete. ## Application boundaries -- `Accounts`: users, authentication, social identities, privacy preferences, - blocks, and roles. +- `Accounts`: users, email/password authentication, private authentication + identities, public social identities, privacy preferences, blocks, and + roles. +- `GoogleAuth`: optional Google OpenID Connect boundary for registration, + sign-in, and account linking. It uses state, nonce, and PKCE, returns only a + normalized verified-email identity, and does not expose provider tokens to + controllers or persistence. - `SocialOAuth`: optional provider boundary for verified social linking. The GitHub adapter uses OAuth state and PKCE, returns only normalized identity attributes to the controller, and does not expose the provider token to the diff --git a/docs/operations.md b/docs/operations.md index 19cd071..9dbce4e 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -59,7 +59,10 @@ do not copy the temporary VPN value into an unrelated server. Configure the transactional SMTP relay and sender accepted by that provider. Provider-specific auth, TLS, ports, and credentials can be supplied to the initializer through the documented `PRODUCTION_SMTP_*` environment values or -edited in the resulting ignored file. Then run: +edited in the resulting ignored file. If Google registration/sign-in is +enabled, also set both Google Web client credentials and register +`https://YOUR_PHX_HOST/auth/google/callback` as the exact authorized redirect +URI. Leave both credentials empty to keep the feature disabled. Then run: ```bash ./scripts/validate-production-env.sh .env.production whoneedhelp.com @@ -73,8 +76,9 @@ docker compose \ The production override keeps Mailpit behind its inactive `local-mail` profile, so public registration cannot appear to succeed while mail is only retained locally. The validator checks file ownership/mode, origin consistency, -template markers, independent generated secrets, supported SMTP values, the -generated database URL, and the final Compose render without printing secrets. +template markers, independent generated secrets, supported SMTP values, a +complete-or-empty Google credential pair, the generated database URL, and the +final Compose render without printing secrets. It does not contact DNS, TLS, SMTP, the reverse proxy, or the application. After deployment, verify `/healthz/ready`, inspect all replica health and logs, register a unique address through the public browser, receive its message at diff --git a/docs/product-spec.md b/docs/product-spec.md index efc9747..a85383d 100644 --- a/docs/product-spec.md +++ b/docs/product-spec.md @@ -38,7 +38,8 @@ helper per request. Every state transition is authorized and recorded. ## MVP scope -- Email magic-link/password authentication. +- Passwordless email magic-link authentication, optional passwords, and + optional Google OpenID Connect registration/sign-in. - English default interface plus Ukrainian and Russian translations. - Medicine pickup/delivery requests. - Safe, non-emergency fuel delivery, car-wheel help, bicycle and motorcycle @@ -77,8 +78,9 @@ helper per request. Every state transition is authorized and recorded. - Multiple simultaneous helpers on one request. - Production-signed Android release, store publication, and native iOS application. -- OAuth verification for providers other than GitHub and automated - social-network identity scoring. +- OAuth verification of public social-profile badges for providers other than + GitHub, and automated social-network identity scoring. Google authentication + is a private sign-in identity, not a public verified social-profile badge. - Automatic punitive abuse enforcement and unapproved production thresholds. - Claims that identity, safety, or fraud prevention is perfect. @@ -96,6 +98,12 @@ configured GitHub OAuth flow can set `verified_at` after provider authorization; one provider account cannot be linked to two local accounts. The provider access token is discarded rather than stored. +Google OpenID Connect identities are stored separately from public social +links. A verified Google email can create a confirmed local account after the +same 18+ safety acknowledgement, or can be connected from a recently +reauthenticated account-settings session. Matching email addresses never merge +accounts automatically, and Google provider tokens are not persisted. + ## Privacy settings Every user chooses one location visibility level: diff --git a/docs/verification.md b/docs/verification.md index 0b264c1..2ea4679 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -16,16 +16,38 @@ results from product limits and unknown production properties. | Consent-driven live tracking | Implemented and cross-client verified | On API 37, Android started `TrackingService` as a location foreground service with a persistent Stop notification. After Home minimized the Activity, an emulator coordinate change reached PostGIS. Notification Stop removed the service, notification, active session, and raw position. | Browsers stop with the page. Android has no `ACCESS_BACKGROUND_LOCATION`, unattended start, or route history. | | Privacy settings | Implemented and browser-verified | The profile exposed hidden, approximate public, exact for active match, and explicit exact-public options. Blocking and current-position cleanup have automated tests. | Exact public location remains a user opt-in; legal privacy and retention text still requires jurisdiction-specific review before launch. | | Reputation and anti-abuse | Implemented at MVP level | Handover codes, two-party completion, double-blind reviews, unique-counterpart ranking, optional movement/proximity evidence, reports, blocks, abuse signals, and moderator audit paths have automated tests. | The system is not bot-proof and does not claim identity verification. No punitive numeric policy is enabled without measured and approved thresholds. | -| Social profiles | Manual links implemented; optional GitHub verification implemented and automated-tested | Manual links cannot set verification fields. The optional GitHub flow uses state, PKCE, a user-bound one-time session, unique provider ownership, and an audit record; the current 186-test suite includes callback replay/state checks. The local protocol drill also performs real HTTP token/user exchanges without returning an access token to the application. | GitHub OAuth credentials are intentionally absent and are not required for registration or the help flow. The real external provider redirect/callback remains disabled and unverified; other providers remain manual/unverified. | +| Account registration and sign-in | Implemented and browser-verified | Email registration sends a confirmation magic link and does not require a password. Confirmed users can keep using magic links or add a password in settings. Google OpenID Connect registration, sign-in, link, unlink, replay prevention, verified-email enforcement, and account-ownership rules are covered by the 260-test suite. A headed Chrome run completed registration, confirmation through Mailpit, password setup, logout, password login, and localized settings with zero console warnings or errors. | The local Compose environment captures email in Mailpit. A production SMTP relay and a real Google Web client are not configured or externally verified. | +| Social profiles | Manual links implemented; optional GitHub verification implemented and automated-tested | Manual links cannot set verification fields. The optional GitHub flow uses state, PKCE, a user-bound one-time session, unique provider ownership, and an audit record. The local protocol drill also performs real HTTP token/user exchanges without returning an access token to the application. | GitHub OAuth credentials are intentionally absent and are not required for registration or the help flow. The real external provider redirect/callback remains disabled and unverified; other providers remain manual/unverified. | | Voluntary thanks | Implemented as an external optional link | A helper can expose an optional link after completion; the UI states that the platform does not process the payment. | The platform does not provide payments, escrow, refunds, tax reporting, or payment guarantees. | | Android client | Local and public-staging clients implemented and emulator-verified | The native packages `org.whoneedhelp.mobile.debug` and `org.whoneedhelp.mobile.staging` launch the same authenticated LiveView app. Seven lifecycle, permission, deep-link, foreground tracking, recoverable main-page failure, notification-Stop, and Activity-destruction tests passed on each of API 30, 34, and 37. The API 37 staging smoke asserted the public home and Safety DOM over HTTPS. A run-scoped Android/browser staging test passed login, private chat in both directions, foreground tracking, live marker appearance/removal, and exact cleanup. | Production signing, Play Store publication, verified Android App Links, unattended/background-permission tracking, and iOS are not implemented. | | Multiple web/worker instances | Implemented and locally failure/rollout-verified | The final isolated Compose drill passed BEAM crashes and sequential replacement with 3 web/2 worker replicas: all five nodes joined, PubSub passed, and 744/744 readiness requests succeeded. The project-owned kind cluster replaced all 2 web/2 worker pod UIDs under `maxUnavailable=0`; all four replacement pods joined, PubSub passed, and 363/363 samples ultimately succeeded. | Local PostGIS is a single instance. Production database HA, backups, and recovery are operator work and are not claimed complete. | | Local observability | Implemented and protocol-verified | Pinned Prometheus scraped the exact 3 web and 2 worker targets with a file Bearer credential; Grafana provisioned a healthy datasource and ten-panel web/worker/BEAM/Ecto/Oban dashboard; Alertmanager delivered firing and resolved webhooks for an induced scoped replica stop. | Local delivery does not establish production retention, notification-provider reliability, on-call policy, or measured alert thresholds. | | Encrypted local backup | Implemented and failure-verified | Pinned Restic streamed PostgreSQL custom format into pinned local MinIO with no host plaintext dump, passed full-data checking and a fresh-database restore, rejected a corrupted repository, and published no snapshot for an interrupted upload. The one-run MinIO project and volume were removed after retaining the non-secret evidence. | The drill proves the local mechanism, not off-site durability, database HA, or a production RPO/RTO/retention policy. | -| External protocol boundaries | Implemented and locally failure-verified | The production release used its configured Assent/Req and Swoosh/gen_smtp clients against an internal-only mock; OAuth and SMTP success/rejection/retry/replay/timeout paths passed. The HTTP push boundary passed disabled, retry, rejection, timeout, and idempotency paths. Request acceptance and new-chat transactions created durable jobs processed by two Oban worker replicas; the chat event completed on Oban attempt 2 after an injected temporary failure. | This does not verify external provider availability or device delivery. FCM/APNs token registration and provider selection remain external work; SMTP exactly-once delivery is not claimed. | +| External protocol boundaries | Implemented and locally failure-verified | The production release used its configured Assent/Req and Swoosh/gen_smtp clients against internal-only mocks. GitHub OAuth, Google OIDC discovery/authorization/token/JWKS with nonce and PKCE, and SMTP success/rejection/retry/replay/timeout paths passed. The HTTP push boundary passed disabled, retry, rejection, timeout, and idempotency paths. Request acceptance and new-chat transactions created durable jobs processed by two Oban worker replicas; the chat event completed on Oban attempt 2 after an injected temporary failure. | This does not verify real external provider availability or device delivery. Google/GitHub production clients, production SMTP, FCM/APNs token registration, and provider selection remain external work; SMTP exactly-once delivery is not claimed. | ## Reproducible checks +- On 2026-07-20, after adding Google OpenID Connect authentication, + `./scripts/test.sh` and `mix precommit` each passed 260 tests with zero + failures. The full isolated `./scripts/quality.sh` gate also passed format, + compilation with warnings as errors, xref, Credo, Sobelow, Dialyzer, Hex and + npm audits, Compose/Helm rendering, environment validation, and configured + HIGH/CRITICAL source/image scans. +- The Google boundary drill used the production Assent adapter against a local + OIDC server with discovery, authorization, token, signed ID token, JWKS, + state, nonce, PKCE, unverified-email rejection, and one-time callback replay + checks. It passed together with GitHub OAuth, SMTP, push, and two-worker Oban + paths. Evidence is retained at + `output/external-boundaries/google-auth-fixed-20260720`. +- Headed Chrome verified the public temporary HTTPS origin through the + email-only registration form, Mailpit confirmation link, one-time login, + password creation, logout, password login, Russian locale selection, and + Google connection settings. The configured Google credential pair is empty, + so the UI correctly left Google actions disabled. Browser console inspection + reported zero errors and zero warnings. The run-owned account and its one + cascading login token were removed after read-only relationship checks; no + help, activity, message, tracking, review, or OAuth rows belonged to it. The + browser was left open. - The isolated Phoenix suite completed on 2026-07-19 with 172 tests and 0 failures after cursor pagination, database aggregation, and the full localization changes @@ -242,6 +264,14 @@ decoded lengths were inspected: the PostgreSQL password is 64 characters and each application secret is 128 characters. Secret values were not printed or written to tracked files. +Before the local `auth_identities` migration, a PostgreSQL custom-format dump +was created at `output/backups/compose-20260720-203913.dump`; its SHA-256 is +`ed8d663b114f3823479198005857af5501a90dacbec3f0855296327ee9cd6d20`. +An isolated restore drill read 23 public tables, 1,488 rows, 12 applied +migrations, and PostGIS 3.6.4 from that dump, then removed the temporary +database. The source Compose database subsequently reported 13 applied +migrations and an empty `auth_identities` table before browser verification. + ## Dependency-upgrade observations - The running Compose and kind releases reported Elixir 1.20.2 and Erlang/OTP @@ -1031,6 +1061,10 @@ is `output/external-boundaries/preprod-gitea-pass`. and the availability model selected for real usage. - Configure a real transactional email provider before public registration. The current Compose mail path terminates at local Mailpit. +- Create a Google OAuth Web client for the final owned HTTPS origin, configure + its exact `/auth/google/callback` redirect, and exercise registration, + sign-in, and settings linking against Google if Google authentication is to + be enabled. The feature remains disabled when both credentials are empty. - Configure and verify a real mobile push provider and device-token lifecycle if native push is required. The provider-neutral HTTP boundary and product jobs are tested; FCM/APNs device delivery is not. diff --git a/e2e/tests/helpers.ts b/e2e/tests/helpers.ts index 27cf3e0..1d230b0 100644 --- a/e2e/tests/helpers.ts +++ b/e2e/tests/helpers.ts @@ -315,12 +315,15 @@ export async function registerAndConfirm( const page = await context.newPage(); await gotoWithTransientRetry(page, "/users/register"); - await page.getByLabel("Email").fill(email); - await page.getByLabel("Display name").fill(displayName); - await page + const form = page.locator("#email_registration_form"); + await form.getByLabel("Email").fill(email); + await form.getByLabel("Display name").fill(displayName); + await form .getByLabel("I am 18 or older and accept the safety rules") .check(); - await page.getByRole("button", { name: "Create an account" }).click(); + await form + .getByRole("button", { name: "Create account and email me a link" }) + .click(); await expect(page).toHaveURL(/\/users\/log-in$/); await expect( page @@ -352,7 +355,7 @@ export async function loginWithMagicLink( await gotoWithTransientRetry(page, "/users/log-in"); const form = page.locator("#login_form_magic"); await form.getByLabel("Email").fill(email); - await form.getByRole("button", { name: "Log in with email" }).click(); + await form.getByRole("button", { name: "Email me a sign-in link" }).click(); await expect( page.getByText( "If your email is in our system, you will receive instructions for logging in shortly.", diff --git a/lib/who_need_help/accounts.ex b/lib/who_need_help/accounts.ex index afcf016..521e07d 100644 --- a/lib/who_need_help/accounts.ex +++ b/lib/who_need_help/accounts.ex @@ -7,7 +7,14 @@ defmodule WhoNeedHelp.Accounts do alias WhoNeedHelp.Pagination alias WhoNeedHelp.Repo - alias WhoNeedHelp.Accounts.{Scope, SocialIdentity, User, UserToken, UserNotifier} + alias WhoNeedHelp.Accounts.{ + AuthIdentity, + Scope, + SocialIdentity, + User, + UserNotifier, + UserToken + } ## Database getters @@ -21,6 +28,14 @@ defmodule WhoNeedHelp.Accounts do :user_id, :inserted_at ] + @registration_key_atoms %{ + "display_name" => :display_name, + "email" => :email, + "email_verified" => :email_verified, + "locale" => :locale, + "provider_uid" => :provider_uid, + "terms_accepted" => :terms_accepted + } @doc """ Returns the deliberately small user projection used by public and @@ -340,6 +355,260 @@ defmodule WhoNeedHelp.Accounts do end end + ## Authentication identities + + def google_auth_connected?(%User{id: user_id}) do + Repo.exists?( + from identity in AuthIdentity, + where: identity.user_id == ^user_id and identity.provider == :google + ) + end + + def login_user_by_google(identity_attrs) do + with {:ok, identity_attrs} <- normalize_google_identity(identity_attrs) do + Repo.transact(fn -> + identity = + AuthIdentity + |> where( + [identity], + identity.provider == :google and + identity.provider_uid == ^identity_attrs.provider_uid + ) + |> lock("FOR UPDATE") + |> Repo.one() + + case identity do + nil -> + {:error, :not_linked} + + %AuthIdentity{} = identity -> + with %User{} = user <- + User + |> where([user], user.id == ^identity.user_id) + |> lock("FOR UPDATE") + |> Repo.one(), + true <- user.moderation_status != :suspended, + {:ok, _identity} <- + identity + |> AuthIdentity.changeset(%{email: identity_attrs.email}) + |> Repo.update() do + {:ok, user} + else + _missing_or_suspended -> {:error, :not_found} + end + end + end) + end + end + + def register_user_by_google(identity_attrs, registration_attrs) + when is_map(registration_attrs) do + with {:ok, identity_attrs} <- normalize_google_identity(identity_attrs) do + Repo.transact(fn -> + identity = + AuthIdentity + |> where( + [identity], + identity.provider == :google and + identity.provider_uid == ^identity_attrs.provider_uid + ) + |> lock("FOR UPDATE") + |> Repo.one() + + case identity do + %AuthIdentity{} = identity -> + google_identity_user(identity, identity_attrs.email) + + nil -> + register_google_identity(identity_attrs, registration_attrs) + end + end) + end + end + + def register_user_by_google(_identity_attrs, _registration_attrs), + do: {:error, :invalid_registration} + + def link_google_identity(%User{id: user_id}, identity_attrs) do + with {:ok, identity_attrs} <- normalize_google_identity(identity_attrs) do + Repo.transact(fn -> + user = + User + |> where([user], user.id == ^user_id) + |> lock("FOR UPDATE") + |> Repo.one() + + cond do + is_nil(user) or user.moderation_status == :suspended -> + {:error, :not_found} + + true -> + upsert_google_identity_for_user(user, identity_attrs) + end + end) + end + end + + def unlink_google_identity(%User{id: user_id}) do + Repo.transact(fn -> + identity = + AuthIdentity + |> where( + [identity], + identity.provider == :google and identity.user_id == ^user_id + ) + |> lock("FOR UPDATE") + |> Repo.one() + + case identity do + %AuthIdentity{} = identity -> Repo.delete(identity) + nil -> {:error, :not_found} + end + end) + end + + defp register_google_identity(identity_attrs, registration_attrs) do + existing_user = + User + |> where([user], user.email == ^identity_attrs.email) + |> lock("FOR UPDATE") + |> Repo.one() + + if existing_user do + {:error, :email_already_registered} + else + attrs = %{ + "email" => identity_attrs.email, + "display_name" => identity_attrs.display_name, + "locale" => registration_value(registration_attrs, "locale", "en"), + "terms_accepted" => registration_value(registration_attrs, "terms_accepted", false) + } + + with {:ok, user} <- register_user(attrs), + {:ok, user} <- user |> User.confirm_changeset() |> Repo.update(), + {:ok, identity} <- + %AuthIdentity{} + |> AuthIdentity.changeset(%{ + provider: :google, + provider_uid: identity_attrs.provider_uid, + email: identity_attrs.email, + user_id: user.id + }) + |> Repo.insert() do + {:ok, {user, identity}} + end + end + end + + defp google_identity_user(identity, current_email) do + user = + User + |> where([user], user.id == ^identity.user_id) + |> lock("FOR UPDATE") + |> Repo.one() + + with %User{} = user <- user, + true <- user.moderation_status != :suspended, + {:ok, identity} <- + identity + |> AuthIdentity.changeset(%{email: current_email}) + |> Repo.update() do + {:ok, {user, identity}} + else + _missing_or_suspended -> {:error, :not_found} + end + end + + defp upsert_google_identity_for_user(user, identity_attrs) do + provider_identity = + AuthIdentity + |> where( + [identity], + identity.provider == :google and + identity.provider_uid == ^identity_attrs.provider_uid + ) + |> lock("FOR UPDATE") + |> Repo.one() + + user_identity = + AuthIdentity + |> where( + [identity], + identity.provider == :google and identity.user_id == ^user.id + ) + |> lock("FOR UPDATE") + |> Repo.one() + + cond do + provider_identity && provider_identity.user_id != user.id -> + {:error, :already_linked} + + user_identity && user_identity.provider_uid != identity_attrs.provider_uid -> + {:error, :provider_already_linked} + + identity = provider_identity || user_identity -> + identity + |> AuthIdentity.changeset(%{email: identity_attrs.email}) + |> Repo.update() + + true -> + %AuthIdentity{} + |> AuthIdentity.changeset(%{ + provider: :google, + provider_uid: identity_attrs.provider_uid, + email: identity_attrs.email, + user_id: user.id + }) + |> Repo.insert() + end + end + + defp normalize_google_identity(identity_attrs) when is_map(identity_attrs) do + provider_uid = registration_value(identity_attrs, "provider_uid", nil) + email = registration_value(identity_attrs, "email", nil) + email_verified = registration_value(identity_attrs, "email_verified", false) + display_name = registration_value(identity_attrs, "display_name", nil) + + cond do + email_verified != true -> + {:error, :email_not_verified} + + not is_binary(provider_uid) or provider_uid == "" or byte_size(provider_uid) > 255 -> + {:error, :invalid_provider_identity} + + not is_binary(email) or email == "" or byte_size(email) > 160 -> + {:error, :invalid_provider_identity} + + not is_binary(display_name) or String.trim(display_name) == "" -> + {:error, :invalid_provider_identity} + + true -> + {:ok, + %{ + provider_uid: provider_uid, + email: email |> String.trim() |> String.downcase(), + email_verified: true, + display_name: display_name |> String.trim() |> String.slice(0, 80) + }} + end + end + + defp normalize_google_identity(_identity_attrs), + do: {:error, :invalid_provider_identity} + + defp registration_value(attrs, key, default) do + case Map.fetch(attrs, key) do + {:ok, value} -> + value + + :error -> + case Map.fetch(@registration_key_atoms, key) do + {:ok, atom_key} -> Map.get(attrs, atom_key, default) + :error -> default + end + end + end + ## Settings @doc """ diff --git a/lib/who_need_help/accounts/auth_identity.ex b/lib/who_need_help/accounts/auth_identity.ex new file mode 100644 index 0000000..db9bbb5 --- /dev/null +++ b/lib/who_need_help/accounts/auth_identity.ex @@ -0,0 +1,36 @@ +defmodule WhoNeedHelp.Accounts.AuthIdentity do + use Ecto.Schema + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + + schema "auth_identities" do + field :provider, Ecto.Enum, values: [:google] + field :provider_uid, :string + field :email, :string + belongs_to :user, WhoNeedHelp.Accounts.User + + timestamps(type: :utc_datetime) + end + + def changeset(identity, attrs) do + identity + |> cast(attrs, [:provider, :provider_uid, :email, :user_id]) + |> update_change(:email, &normalize_email/1) + |> validate_required([:provider, :provider_uid, :email, :user_id]) + |> validate_inclusion(:provider, [:google]) + |> validate_length(:provider_uid, min: 1, max: 255) + |> validate_format(:email, ~r/^[^@,;\s]+@[^@,;\s]+$/, + message: "must have the @ sign and no spaces" + ) + |> validate_length(:email, max: 160) + |> unique_constraint([:provider, :provider_uid]) + |> unique_constraint([:user_id, :provider]) + end + + defp normalize_email(email) when is_binary(email), + do: email |> String.trim() |> String.downcase() + + defp normalize_email(email), do: email +end diff --git a/lib/who_need_help/external_boundary_drill.ex b/lib/who_need_help/external_boundary_drill.ex index ef71f2a..131d801 100644 --- a/lib/who_need_help/external_boundary_drill.ex +++ b/lib/who_need_help/external_boundary_drill.ex @@ -17,9 +17,11 @@ defmodule WhoNeedHelp.ExternalBoundaryDrill do alias WhoNeedHelp.Push.DisabledAdapter, as: PushDisabledAdapter alias WhoNeedHelp.Push.HTTPAdapter, as: PushHTTPAdapter alias WhoNeedHelp.Repo + alias WhoNeedHelp.GoogleAuth.AssentAdapter, as: GoogleAssentAdapter alias WhoNeedHelp.SocialOAuth.AssentAdapter @oauth_redirect_uri "http://boundary.local/auth/social/github/callback" + @google_redirect_uri "http://boundary.local/auth/google/callback" @output_directory "/output" @output_path "/output/summary.json" @@ -36,6 +38,7 @@ defmodule WhoNeedHelp.ExternalBoundaryDrill do summary = %{ status: "passed", oauth: oauth_drill(base_url), + google_oidc: google_oidc_drill(base_url), smtp: smtp_drill(base_url), push: Map.put(protocol_push, :domain_workflow_integration, product_push.status), push_product_integration: product_push @@ -49,6 +52,118 @@ defmodule WhoNeedHelp.ExternalBoundaryDrill do :ok end + defp google_oidc_drill(base_url) do + assert!(GoogleAssentAdapter.enabled?(), "runtime Google OIDC config is disabled") + + control!(base_url, "google", "success") + {success_session, success_params} = google_authorization!() + + {:ok, identity} = + GoogleAssentAdapter.callback( + @google_redirect_uri, + success_params, + success_session + ) + + assert!( + identity.provider_uid == "google-local-subject", + "Google subject was not normalized" + ) + + assert!( + identity.email == "google-helper@example.invalid", + "Google email was not normalized" + ) + + assert!( + not Map.has_key?(identity, :access_token), + "Google access token escaped adapter boundary" + ) + + success_state = state!(base_url)["google"] + assert!(success_state["token_requests"] == 1, "Google token request was not observed") + assert!(success_state["jwks_requests"] == 1, "Google JWKS request was not observed") + assert!(success_state["consumed_codes"] == 1, "Google code was not consumed") + + assert_error!( + GoogleAssentAdapter.callback( + @google_redirect_uri, + success_params, + success_session + ), + "Google authorization code replay unexpectedly succeeded" + ) + + replay_state = state!(base_url)["google"] + assert!(replay_state["token_requests"] == 2, "Google replay did not reach token endpoint") + assert!(replay_state["jwks_requests"] == 1, "Google replay reached the JWKS endpoint") + + control!(base_url, "google", "success") + {mismatch_session, mismatch_params} = google_authorization!() + + assert_error!( + GoogleAssentAdapter.callback( + @google_redirect_uri, + Map.put(mismatch_params, "state", "mismatched-state"), + mismatch_session + ), + "Google state mismatch unexpectedly succeeded" + ) + + mismatch_state = state!(base_url)["google"] + assert!(mismatch_state["token_requests"] == 0, "Google state mismatch reached token endpoint") + + control!(base_url, "google", "nonce_mismatch") + {nonce_session, nonce_params} = google_authorization!() + + assert_error!( + GoogleAssentAdapter.callback( + @google_redirect_uri, + nonce_params, + nonce_session + ), + "Google nonce mismatch unexpectedly succeeded" + ) + + nonce_state = state!(base_url)["google"] + assert!(nonce_state["token_requests"] == 1, "Google nonce test skipped token exchange") + assert!(nonce_state["jwks_requests"] == 1, "Google nonce test skipped signature verification") + + control!(base_url, "google", "unverified_email") + {email_session, email_params} = google_authorization!() + + assert!( + GoogleAssentAdapter.callback( + @google_redirect_uri, + email_params, + email_session + ) == {:error, :email_not_verified}, + "Google unverified email was accepted" + ) + + %{ + success: "passed", + state_mismatch_blocked_before_token: true, + one_time_code_replay_rejected: true, + nonce_mismatch_rejected_after_signature_verification: true, + unverified_email_rejected: true, + access_token_returned_to_application: false + } + end + + defp google_authorization! do + {:ok, %{url: url, session_params: session_params}} = + GoogleAssentAdapter.authorize_url(@google_redirect_uri) + + response = Req.get!(url, redirect: false, retry: false) + assert!(response.status == 302, "Google OIDC mock did not redirect") + + [location] = Req.Response.get_header(response, "location") + params = location |> URI.parse() |> Map.fetch!(:query) |> URI.decode_query() + + {session_params, params} + end + defp oauth_drill(base_url) do assert!(AssentAdapter.enabled?(:github), "runtime GitHub OAuth config is disabled") diff --git a/lib/who_need_help/google_auth.ex b/lib/who_need_help/google_auth.ex new file mode 100644 index 0000000..1c1a3f4 --- /dev/null +++ b/lib/who_need_help/google_auth.ex @@ -0,0 +1,35 @@ +defmodule WhoNeedHelp.GoogleAuth do + @moduledoc """ + Provider boundary for Google OpenID Connect authentication. + + Provider access and ID tokens are consumed inside the adapter and are never + returned to controllers or persisted by the application. + """ + + @type identity :: %{ + provider_uid: String.t(), + email: String.t(), + email_verified: true, + display_name: String.t() + } + + @callback enabled?() :: boolean() + @callback authorize_url(String.t()) :: + {:ok, %{url: String.t(), session_params: map()}} | {:error, term()} + @callback callback(String.t(), map(), map()) :: {:ok, identity()} | {:error, term()} + + def enabled?, do: adapter().enabled?() + + def authorize_url(redirect_uri), do: adapter().authorize_url(redirect_uri) + + def callback(redirect_uri, params, session_params), + do: adapter().callback(redirect_uri, params, session_params) + + defp adapter do + Application.get_env( + :who_need_help, + :google_auth_adapter, + WhoNeedHelp.GoogleAuth.AssentAdapter + ) + end +end diff --git a/lib/who_need_help/google_auth/assent_adapter.ex b/lib/who_need_help/google_auth/assent_adapter.ex new file mode 100644 index 0000000..3418d1c --- /dev/null +++ b/lib/who_need_help/google_auth/assent_adapter.ex @@ -0,0 +1,91 @@ +defmodule WhoNeedHelp.GoogleAuth.AssentAdapter do + @moduledoc false + + @behaviour WhoNeedHelp.GoogleAuth + + alias Assent.Strategy.Google + + @impl true + def enabled?, do: not is_nil(provider_config()) + + @impl true + def authorize_url(redirect_uri) do + with {:ok, config} <- config(redirect_uri) do + config + |> Keyword.put(:nonce, random_url_token(32)) + |> Keyword.put(:code_verifier, true) + |> Google.authorize_url() + end + end + + @impl true + def callback(redirect_uri, params, session_params) do + with {:ok, config} <- config(redirect_uri), + {:ok, %{user: user}} <- + config + |> Keyword.put(:code_verifier, true) + |> Keyword.put(:session_params, session_params) + |> Google.callback(params) do + normalize_identity(user) + end + end + + def normalize_identity( + %{ + "sub" => provider_uid, + "email" => email, + "email_verified" => true + } = claims + ) + when is_binary(provider_uid) and provider_uid != "" and is_binary(email) and email != "" do + normalized_email = email |> String.trim() |> String.downcase() + + {:ok, + %{ + provider_uid: provider_uid, + email: normalized_email, + email_verified: true, + display_name: display_name(normalized_email, Map.get(claims, "name")) + }} + end + + def normalize_identity(%{"email_verified" => false}), do: {:error, :email_not_verified} + def normalize_identity(_claims), do: {:error, :invalid_provider_identity} + + defp config(redirect_uri) do + case provider_config() do + nil -> {:error, :provider_disabled} + config -> {:ok, Keyword.put(config, :redirect_uri, redirect_uri)} + end + end + + defp provider_config do + case Application.get_env(:who_need_help, :google_auth, []) do + config when is_list(config) and config != [] -> config + _disabled -> nil + end + end + + defp display_name(email, name) when is_binary(name) do + case String.trim(name) do + "" -> fallback_display_name(email) + value -> String.slice(value, 0, 80) + end + end + + defp display_name(email, _name), do: fallback_display_name(email) + + defp fallback_display_name(email) do + candidate = email |> String.split("@", parts: 2) |> hd() |> String.trim() + + if String.length(candidate) >= 2, + do: String.slice(candidate, 0, 80), + else: "Google user" + end + + defp random_url_token(length) do + length + |> :crypto.strong_rand_bytes() + |> Base.url_encode64(padding: false) + end +end diff --git a/lib/who_need_help_web/components/core_components.ex b/lib/who_need_help_web/components/core_components.ex index e59f131..234cb72 100644 --- a/lib/who_need_help_web/components/core_components.ex +++ b/lib/who_need_help_web/components/core_components.ex @@ -123,6 +123,59 @@ defmodule WhoNeedHelpWeb.CoreComponents do end end + @doc """ + Renders the Google authentication action with the standard multicolor G. + + The icon geometry and light button treatment follow Google's published + Sign in with Google branding guidance. The form itself owns the action and + CSRF token so the button remains a normal accessible submit control. + """ + attr :label, :string, required: true + attr :disabled, :boolean, default: false + attr :rest, :global, include: ~w(id name value form) + + def google_auth_button(assigns) do + ~H""" + + """ + end + @doc """ Renders an input with label and error messages. diff --git a/lib/who_need_help_web/controllers/google_auth_controller.ex b/lib/who_need_help_web/controllers/google_auth_controller.ex new file mode 100644 index 0000000..8594cf3 --- /dev/null +++ b/lib/who_need_help_web/controllers/google_auth_controller.ex @@ -0,0 +1,278 @@ +defmodule WhoNeedHelpWeb.GoogleAuthController do + use WhoNeedHelpWeb, :controller + + require Logger + + alias WhoNeedHelp.{Accounts, GoogleAuth, Repo, Trust} + alias WhoNeedHelp.Trust.RateLimiter + alias WhoNeedHelpWeb.UserAuth + + import WhoNeedHelpWeb.UserAuth, only: [require_sudo_mode: 2] + + @session_key "google_auth_flow" + @supported_locales ~w(en uk ru) + + plug :put_no_store + plug :require_sudo_mode when action in [:start_link, :disconnect] + + def start_login(conn, _params) do + start_flow(conn, "login", %{}, ~p"/users/log-in") + end + + def start_registration(conn, %{"google_registration" => params}) when is_map(params) do + locale = normalize_locale(params["locale"]) + terms_accepted = params["terms_accepted"] in [true, "true", "on", "1"] + + if terms_accepted do + start_flow( + conn, + "register", + %{"locale" => locale, "terms_accepted" => true}, + ~p"/users/register" + ) + else + conn + |> put_flash( + :error, + gettext("Confirm that you are 18 or older and accept the safety rules first.") + ) + |> redirect(to: ~p"/users/register") + end + end + + def start_registration(conn, _params) do + conn + |> put_flash(:error, gettext("The Google registration form is invalid.")) + |> redirect(to: ~p"/users/register") + end + + def start_link(conn, _params) do + user = conn.assigns.current_scope.user + start_flow(conn, "link", %{"user_id" => user.id}, ~p"/users/settings") + end + + def disconnect(conn, _params) do + current_user = conn.assigns.current_scope.user + + case unlink_google_identity_and_audit(current_user) do + {:ok, _identity} -> + conn + |> put_flash(:info, gettext("Google sign-in disconnected.")) + |> redirect(to: ~p"/users/settings") + + {:error, :not_found} -> + conn + |> put_flash(:error, gettext("No Google account is connected.")) + |> redirect(to: ~p"/users/settings") + + {:error, _reason} -> + google_account_unavailable(conn, ~p"/users/settings") + end + end + + def callback(conn, params) do + flow = get_session(conn, @session_key) + conn = delete_session(conn, @session_key) + + with %{"flow" => flow_name, "session_params" => session_params} <- flow, + true <- flow_name in ["login", "register", "link"], + {:ok, identity_attrs} <- + GoogleAuth.callback(callback_url(conn), params, session_params) do + finish_flow(conn, flow_name, flow, identity_attrs) + else + nil -> + callback_error( + conn, + flow, + gettext("Google sign-in session expired. Please start again.") + ) + + false -> + callback_error(conn, flow, gettext("Google sign-in session is invalid.")) + + {:error, :email_not_verified} -> + callback_error( + conn, + flow, + gettext("Google did not provide a verified email address.") + ) + + {:error, error} -> + Logger.warning("Google OAuth callback failed (#{error_name(error)})") + callback_error(conn, flow, gettext("Google sign-in failed. Please try again.")) + end + end + + defp start_flow(conn, flow, extra, failure_path) do + if GoogleAuth.enabled?() do + case GoogleAuth.authorize_url(callback_url(conn)) do + {:ok, %{url: url, session_params: session_params}} -> + session = + extra + |> Map.put("flow", flow) + |> Map.put("session_params", session_params) + + conn + |> put_session(@session_key, session) + |> redirect(external: url) + + {:error, error} -> + Logger.warning("Unable to start Google OAuth (#{error_name(error)})") + + conn + |> put_flash(:error, gettext("Google sign-in could not be started.")) + |> redirect(to: failure_path) + end + else + conn + |> put_flash(:error, gettext("Google sign-in is not configured yet.")) + |> redirect(to: failure_path) + end + end + + defp finish_flow(conn, "login", _flow, identity_attrs) do + case Accounts.login_user_by_google(identity_attrs) do + {:ok, user} -> + conn + |> put_flash(:info, gettext("Welcome back!")) + |> UserAuth.log_in_user(user) + + {:error, :not_linked} -> + conn + |> put_flash( + :error, + gettext( + "This Google account is not connected yet. Create an account or sign in by email and connect Google in account settings." + ) + ) + |> redirect(to: ~p"/users/log-in") + + {:error, _reason} -> + google_account_unavailable(conn, ~p"/users/log-in") + end + end + + defp finish_flow(conn, "register", flow, identity_attrs) do + with {:ok, _limit} <- RateLimiter.check(:registration_email, identity_attrs.email), + {:ok, {user, _identity}} <- + Accounts.register_user_by_google(identity_attrs, %{ + "locale" => flow["locale"], + "terms_accepted" => flow["terms_accepted"] + }) do + conn + |> put_flash(:info, gettext("Your account was created with Google.")) + |> UserAuth.log_in_user(user) + else + {:error, :email_already_registered} -> + conn + |> put_flash( + :error, + gettext( + "An account with this email already exists. Sign in by email or password, then connect Google in account settings." + ) + ) + |> redirect(to: ~p"/users/log-in") + + {:error, :rate_limited} -> + conn + |> put_flash( + :error, + gettext("Too many registration attempts in the configured time window.") + ) + |> redirect(to: ~p"/users/register") + + {:error, _reason} -> + google_account_unavailable(conn, ~p"/users/register") + end + end + + defp finish_flow(conn, "link", flow, identity_attrs) do + current_user = get_in(conn.assigns, [:current_scope, Access.key(:user)]) + + if current_user && flow["user_id"] == current_user.id && + Accounts.sudo_mode?(current_user, -10) do + case link_google_identity_and_audit(current_user, identity_attrs) do + {:ok, _identity} -> + conn + |> put_flash(:info, gettext("Google sign-in connected.")) + |> redirect(to: ~p"/users/settings") + + {:error, :already_linked} -> + conn + |> put_flash(:error, gettext("That Google account belongs to another local account.")) + |> redirect(to: ~p"/users/settings") + + {:error, :provider_already_linked} -> + conn + |> put_flash(:error, gettext("A different Google account is already connected.")) + |> redirect(to: ~p"/users/settings") + + {:error, _reason} -> + google_account_unavailable(conn, ~p"/users/settings") + end + else + conn + |> put_flash(:error, gettext("Google account connection session is invalid.")) + |> redirect(to: ~p"/users/log-in") + end + end + + defp link_google_identity_and_audit(current_user, identity_attrs) do + Repo.transact(fn -> + with {:ok, identity} <- Accounts.link_google_identity(current_user, identity_attrs), + {:ok, _audit} <- + Trust.audit( + current_user.id, + "auth_identity.connected", + "auth_identity", + identity.id, + %{"provider" => "google"} + ) do + {:ok, identity} + end + end) + end + + defp unlink_google_identity_and_audit(current_user) do + Repo.transact(fn -> + with {:ok, identity} <- Accounts.unlink_google_identity(current_user), + {:ok, _audit} <- + Trust.audit( + current_user.id, + "auth_identity.disconnected", + "auth_identity", + identity.id, + %{"provider" => "google"} + ) do + {:ok, identity} + end + end) + end + + defp callback_error(conn, flow, message) do + conn + |> put_flash(:error, message) + |> redirect(to: failure_path(flow)) + end + + defp google_account_unavailable(conn, path) do + conn + |> put_flash(:error, gettext("This Google account cannot be used right now.")) + |> redirect(to: path) + end + + defp failure_path(%{"flow" => "register"}), do: ~p"/users/register" + defp failure_path(%{"flow" => "link"}), do: ~p"/users/settings" + defp failure_path(_flow), do: ~p"/users/log-in" + + defp callback_url(conn), do: url(conn, ~p"/auth/google/callback") + + defp normalize_locale(locale) when locale in @supported_locales, do: locale + defp normalize_locale(_locale), do: "en" + + defp error_name(error) when is_atom(error), do: Atom.to_string(error) + defp error_name(%{__struct__: module}), do: inspect(module) + defp error_name(_error), do: "unknown_error" + + defp put_no_store(conn, _opts), do: put_resp_header(conn, "cache-control", "no-store") +end diff --git a/lib/who_need_help_web/controllers/user_registration_controller.ex b/lib/who_need_help_web/controllers/user_registration_controller.ex index 67ec477..c57ab6a 100644 --- a/lib/who_need_help_web/controllers/user_registration_controller.ex +++ b/lib/who_need_help_web/controllers/user_registration_controller.ex @@ -3,7 +3,7 @@ defmodule WhoNeedHelpWeb.UserRegistrationController do require Logger - alias WhoNeedHelp.Accounts + alias WhoNeedHelp.{Accounts, GoogleAuth} alias WhoNeedHelp.Accounts.User alias WhoNeedHelp.Trust.RateLimiter @@ -11,7 +11,7 @@ defmodule WhoNeedHelpWeb.UserRegistrationController do def new(conn, _params) do changeset = Accounts.change_user_registration(%User{}, %{}, validate_unique: false) - render(conn, :new, changeset: changeset) + render_registration(conn, changeset) end def create(conn, %{"user" => user_params}) when is_map(user_params) do @@ -37,7 +37,7 @@ defmodule WhoNeedHelpWeb.UserRegistrationController do registration_response(conn) else - render(conn, :new, changeset: changeset) + render_registration(conn, changeset) end end else @@ -48,9 +48,7 @@ defmodule WhoNeedHelpWeb.UserRegistrationController do :error, gettext("Too many registration attempts in the configured time window.") ) - |> render(:new, - changeset: Accounts.change_user_registration(%User{}, user_params) - ) + |> render_registration(Accounts.change_user_registration(%User{}, user_params)) end end @@ -58,8 +56,8 @@ defmodule WhoNeedHelpWeb.UserRegistrationController do conn |> put_status(:bad_request) |> put_flash(:error, gettext("The registration form is invalid.")) - |> render(:new, - changeset: Accounts.change_user_registration(%User{}, %{}, validate_unique: false) + |> render_registration( + Accounts.change_user_registration(%User{}, %{}, validate_unique: false) ) end @@ -124,5 +122,12 @@ defmodule WhoNeedHelpWeb.UserRegistrationController do end) end + defp render_registration(conn, changeset) do + render(conn, :new, + changeset: changeset, + google_auth_enabled: GoogleAuth.enabled?() + ) + end + defp put_no_store(conn, _opts), do: put_resp_header(conn, "cache-control", "no-store") end diff --git a/lib/who_need_help_web/controllers/user_registration_html/new.html.heex b/lib/who_need_help_web/controllers/user_registration_html/new.html.heex index a02fb5a..ab05a9f 100644 --- a/lib/who_need_help_web/controllers/user_registration_html/new.html.heex +++ b/lib/who_need_help_web/controllers/user_registration_html/new.html.heex @@ -1,5 +1,5 @@ -
+
<.header> {gettext("Register for an account")} @@ -13,7 +13,49 @@
- <.form :let={f} for={@changeset} action={~p"/users/register"}> +

+ {gettext( + "A password is not required. We will email a one-time confirmation link. You can add a password later in account settings." + )} +

+ + <.form + for={%{}} + as={:google_registration} + action={~p"/auth/google/register"} + id="google_registration_form" + class="space-y-3" + > + + <.input + type="checkbox" + id="google_registration_terms" + name="google_registration[terms_accepted]" + value="false" + label={gettext("I am 18 or older and accept the safety rules")} + required + /> + <.google_auth_button + label={gettext("Sign up with Google")} + disabled={!@google_auth_enabled} + /> +

+ {gettext("Google sign-in will be available after the operator configures it.")} +

+ + +
{gettext("or sign up with email")}
+ + <.form + :let={f} + for={@changeset} + action={~p"/users/register"} + id="email_registration_form" + > - <.button phx-disable-with={gettext("Creating account...")} class="btn btn-primary w-full"> - {gettext("Create an account")} + <.button phx-disable-with={gettext("Sending link...")} class="btn btn-primary w-full"> + {gettext("Create account and email me a link")}
diff --git a/lib/who_need_help_web/controllers/user_session_controller.ex b/lib/who_need_help_web/controllers/user_session_controller.ex index ae41ce4..b6821ae 100644 --- a/lib/who_need_help_web/controllers/user_session_controller.ex +++ b/lib/who_need_help_web/controllers/user_session_controller.ex @@ -3,7 +3,7 @@ defmodule WhoNeedHelpWeb.UserSessionController do require Logger - alias WhoNeedHelp.Accounts + alias WhoNeedHelp.{Accounts, GoogleAuth} alias WhoNeedHelp.Trust.RateLimiter alias WhoNeedHelpWeb.UserAuth @@ -126,11 +126,12 @@ defmodule WhoNeedHelpWeb.UserSessionController do end defp assign_magic_link_form(conn, _opts) do - assign( - conn, + conn + |> assign( :magic_link_form, Phoenix.Component.to_form(%{"token" => ""}, as: "user") ) + |> assign(:google_auth_enabled, GoogleAuth.enabled?()) end defp put_no_store(conn, _opts), do: put_resp_header(conn, "cache-control", "no-store") diff --git a/lib/who_need_help_web/controllers/user_session_html/new.html.heex b/lib/who_need_help_web/controllers/user_session_html/new.html.heex index 93a19b1..2eae3a8 100644 --- a/lib/who_need_help_web/controllers/user_session_html/new.html.heex +++ b/lib/who_need_help_web/controllers/user_session_html/new.html.heex @@ -63,6 +63,24 @@
+ <.form + :if={!@current_scope} + for={%{}} + as={:google_login} + action={~p"/auth/google/login"} + id="google_login_form" + > + <.google_auth_button + label={gettext("Sign in with Google")} + disabled={!@google_auth_enabled} + /> +

+ {gettext("Google sign-in will be available after the operator configures it.")} +

+ + +
{gettext("or use email")}
+ <.form :let={f} for={@form} as={:user} id="login_form_magic" action={~p"/users/log-in"}> <.input readonly={!!@current_scope} @@ -74,37 +92,55 @@ required phx-mounted={JS.focus()} /> +

+ {gettext("We will send a one-time sign-in link. No password is required.")} +

<.button class="btn btn-primary w-full"> - {gettext("Log in with email")} + {gettext("Email me a sign-in link")} -
{gettext("or")}
- - <.form :let={f} for={@form} as={:user} id="login_form_password" action={~p"/users/log-in"}> - <.input - readonly={!!@current_scope} - field={f[:email]} - type="email" - label={gettext("Email")} - autocomplete="username" - spellcheck="false" - required - /> - <.input - field={f[:password]} - type="password" - label={gettext("Password")} - autocomplete="current-password" - spellcheck="false" - /> - <.button class="btn btn-primary w-full" name={@form[:remember_me].name} value="true"> - {gettext("Log in and stay logged in")} - - <.button class="btn btn-primary btn-soft w-full mt-2"> - {gettext("Log in only this time")} - - +
+ + {gettext("Use a password instead")} + +
+

+ {gettext("This works only if you previously added a password in account settings.")} +

+ <.form + :let={f} + for={@form} + as={:user} + id="login_form_password" + action={~p"/users/log-in"} + > + <.input + readonly={!!@current_scope} + field={f[:email]} + type="email" + label={gettext("Email")} + autocomplete="username" + spellcheck="false" + required + /> + <.input + field={f[:password]} + type="password" + label={gettext("Password")} + autocomplete="current-password" + spellcheck="false" + required + /> + <.button class="btn btn-primary w-full" name={@form[:remember_me].name} value="true"> + {gettext("Log in and stay logged in")} + + <.button class="btn btn-primary btn-soft w-full mt-2"> + {gettext("Log in only this time")} + + +
+
diff --git a/lib/who_need_help_web/controllers/user_settings_controller.ex b/lib/who_need_help_web/controllers/user_settings_controller.ex index 7869039..3f95026 100644 --- a/lib/who_need_help_web/controllers/user_settings_controller.ex +++ b/lib/who_need_help_web/controllers/user_settings_controller.ex @@ -3,7 +3,7 @@ defmodule WhoNeedHelpWeb.UserSettingsController do require Logger - alias WhoNeedHelp.Accounts + alias WhoNeedHelp.{Accounts, GoogleAuth} alias WhoNeedHelp.Trust.RateLimiter alias WhoNeedHelpWeb.UserAuth @@ -103,6 +103,8 @@ defmodule WhoNeedHelpWeb.UserSettingsController do conn |> assign(:email_changeset, Accounts.change_user_email(user)) |> assign(:password_changeset, Accounts.change_user_password(user)) + |> assign(:google_auth_enabled, GoogleAuth.enabled?()) + |> assign(:google_auth_connected, Accounts.google_auth_connected?(user)) end defp deliver_email_change_instructions(conn, user, changeset) do diff --git a/lib/who_need_help_web/controllers/user_settings_html/edit.html.heex b/lib/who_need_help_web/controllers/user_settings_html/edit.html.heex index d2efc09..849e466 100644 --- a/lib/who_need_help_web/controllers/user_settings_html/edit.html.heex +++ b/lib/who_need_help_web/controllers/user_settings_html/edit.html.heex @@ -25,6 +25,55 @@
+
+

{gettext("Google sign-in")}

+

+ <%= if @google_auth_connected do %> + {gettext("A Google account is connected. You can use it to sign in.")} + <% else %> + {gettext( + "Connect Google only after signing in here. We never merge accounts automatically by matching email addresses." + )} + <% end %> +

+ + <.form + :if={!@google_auth_connected} + for={%{}} + as={:google_link} + action={~p"/auth/google/link"} + id="google_link_form" + class="mt-4" + > + <.google_auth_button + label={gettext("Connect Google account")} + disabled={!@google_auth_enabled} + /> +

+ {gettext("Google sign-in will be available after the operator configures it.")} +

+ + + <.form + :if={@google_auth_connected} + for={%{}} + action={~p"/auth/google/link"} + method="delete" + id="google_unlink_form" + class="mt-4" + > + <.button + variant="primary" + class="btn-soft" + data-confirm={gettext("Disconnect Google sign-in from this account?")} + > + {gettext("Disconnect Google")} + + +
+ +
+ <.form :let={f} for={@password_changeset} action={~p"/users/settings"} id="update_password"> bytes: return json.dumps(value, separators=(",", ":"), sort_keys=True).encode() +def base64url(value: bytes) -> str: + return base64.urlsafe_b64encode(value).rstrip(b"=").decode() + + +def unsigned_bytes(value: int) -> bytes: + return value.to_bytes((value.bit_length() + 7) // 8, "big") + + +def google_id_token(code: dict[str, Any], mode: str) -> str: + now = int(time.time()) + header = {"alg": "RS256", "kid": GOOGLE_KEY_ID, "typ": "JWT"} + claims = { + "iss": GOOGLE_ISSUER, + "sub": "google-local-subject", + "aud": OAUTH_CLIENT_ID, + "exp": now + 300, + "iat": now, + "nonce": ( + "invalid-nonce" if mode == "nonce_mismatch" else code["nonce"] + ), + "email": "google-helper@example.invalid", + "email_verified": mode != "unverified_email", + "name": "Google Local Helper", + } + signing_input = ( + f"{base64url(json_bytes(header))}.{base64url(json_bytes(claims))}" + ).encode() + digest_info = bytes.fromhex("3031300d060960864801650304020105000420") + digest_info += hashlib.sha256(signing_input).digest() + padding = b"\xff" * (256 - len(digest_info) - 3) + encoded_message = b"\x00\x01" + padding + b"\x00" + digest_info + signature = pow( + int.from_bytes(encoded_message, "big"), + GOOGLE_RSA_PRIVATE_EXPONENT, + GOOGLE_RSA_MODULUS, + ).to_bytes(256, "big") + return f"{signing_input.decode()}.{base64url(signature)}" + class BoundaryHTTPServer(ThreadingHTTPServer): daemon_threads = True @@ -151,6 +239,12 @@ class BoundaryHTTPHandler(BaseHTTPRequestHandler): self.oauth_authorize(parsed) elif parsed.path == "/oauth/user": self.oauth_user() + elif parsed.path == "/.well-known/openid-configuration": + self.google_discovery() + elif parsed.path == "/google/authorize": + self.google_authorize(parsed) + elif parsed.path == "/google/jwks": + self.google_jwks() else: self.send_json(404, {"error": "not_found"}) @@ -167,6 +261,8 @@ class BoundaryHTTPHandler(BaseHTTPRequestHandler): self.control(body) elif parsed.path == "/oauth/token": self.oauth_token(body) + elif parsed.path == "/google/token": + self.google_token(body) elif parsed.path == "/push": self.push(body) else: @@ -314,6 +410,139 @@ class BoundaryHTTPHandler(BaseHTTPRequestHandler): }, ) + def google_discovery(self) -> None: + with STATE.lock: + STATE.google_discovery_requests += 1 + + self.send_json( + 200, + { + "issuer": GOOGLE_ISSUER, + "authorization_endpoint": f"{GOOGLE_ISSUER}/google/authorize", + "token_endpoint": f"{GOOGLE_ISSUER}/google/token", + "jwks_uri": f"{GOOGLE_ISSUER}/google/jwks", + "token_endpoint_auth_methods_supported": ["client_secret_post"], + }, + ) + + def google_authorize(self, parsed: urllib.parse.SplitResult) -> None: + params = urllib.parse.parse_qs(parsed.query) + required = { + "client_id", + "redirect_uri", + "state", + "nonce", + "scope", + "code_challenge", + "code_challenge_method", + } + + if ( + not required.issubset(params) + or params["client_id"][0] != OAUTH_CLIENT_ID + or params["code_challenge_method"][0] != "S256" + or set(params["scope"][0].split()) != {"openid", "email", "profile"} + ): + self.send_json(400, {"error": "invalid_google_authorization_request"}) + return + + redirect_uri = params["redirect_uri"][0] + state = params["state"][0] + + with STATE.lock: + STATE.google_authorize_requests += 1 + STATE.google_code_counter += 1 + code = f"google-boundary-code-{STATE.google_code_counter}" + STATE.google_codes[code] = { + "challenge": params["code_challenge"][0], + "redirect_uri": redirect_uri, + "nonce": params["nonce"][0], + "used": False, + } + + location = ( + f"{redirect_uri}?{urllib.parse.urlencode({'code': code, 'state': state})}" + ) + self.send_response(302) + self.send_header("location", location) + self.send_header("content-length", "0") + self.end_headers() + + def google_token(self, body: bytes) -> None: + params = { + key: values[0] + for key, values in urllib.parse.parse_qs(body.decode()).items() + } + + with STATE.lock: + STATE.google_token_requests += 1 + mode = STATE.google_mode + code = STATE.google_codes.get(params.get("code", "")) + + verifier = params.get("code_verifier", "") + challenge = base64url(hashlib.sha256(verifier.encode()).digest()) + checks = { + "client_id": params.get("client_id") == OAUTH_CLIENT_ID, + "client_secret": params.get("client_secret") == OAUTH_CLIENT_SECRET, + "grant_type": params.get("grant_type") == "authorization_code", + "known_code": code is not None, + "unused_code": code is not None and not code["used"], + "redirect_uri": ( + code is not None and code["redirect_uri"] == params.get("redirect_uri") + ), + "pkce": code is not None and code["challenge"] == challenge, + } + valid = all(checks.values()) + + if not valid: + failed_checks = sorted(name for name, passed in checks.items() if not passed) + if not checks["pkce"] and code is not None: + failed_checks.append( + "pkce_lengths_" + f"{len(verifier)}_{len(code['challenge'])}_{len(challenge)}" + ) + self.send_json( + 400, + { + "error": "invalid_grant", + "error_description": ",".join(failed_checks), + }, + ) + return + + with STATE.lock: + code["used"] = True + + self.send_json( + 200, + { + "access_token": "google-boundary-access-token", + "id_token": google_id_token(code, mode), + "scope": "openid email profile", + "token_type": "Bearer", + }, + ) + + def google_jwks(self) -> None: + with STATE.lock: + STATE.google_jwks_requests += 1 + + self.send_json( + 200, + { + "keys": [ + { + "alg": "RS256", + "e": base64url(unsigned_bytes(GOOGLE_RSA_EXPONENT)), + "kid": GOOGLE_KEY_ID, + "kty": "RSA", + "n": base64url(unsigned_bytes(GOOGLE_RSA_MODULUS)), + "use": "sig", + } + ] + }, + ) + def push(self, body: bytes) -> None: try: notification = json.loads(body) diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index c66ac0b..0c4b56a 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -17,7 +17,7 @@ msgstr "" msgid "Account settings" msgstr "" -#: lib/who_need_help_web/components/core_components.ex:393 +#: lib/who_need_help_web/components/core_components.ex:446 #, elixir-autogen, elixir-format msgid "Actions" msgstr "" @@ -25,7 +25,7 @@ msgstr "" #: lib/who_need_help_web/components/layouts.ex:70 #: lib/who_need_help_web/components/layouts.ex:119 #: lib/who_need_help_web/controllers/page_html/home.html.heex:21 -#: lib/who_need_help_web/live/request_live/new.ex:22 +#: lib/who_need_help_web/live/request_live/new.ex:23 #, elixir-autogen, elixir-format msgid "Ask for help" msgstr "" @@ -108,7 +108,7 @@ msgstr "" msgid "close" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:363 +#: lib/who_need_help_web/live/activity_live/show.ex:367 #, elixir-autogen, elixir-format msgid "%{approved}/%{capacity} approved" msgstr "" @@ -118,37 +118,37 @@ msgstr "" msgid "%{count} community votes" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:745 +#: lib/who_need_help_web/live/request_live/show.ex:750 #, elixir-autogen, elixir-format msgid "%{count} unique people" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:829 +#: lib/who_need_help_web/live/request_live/show.ex:842 #, elixir-autogen, elixir-format msgid "1 — unsafe" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:828 +#: lib/who_need_help_web/live/request_live/show.ex:841 #, elixir-autogen, elixir-format msgid "2 — poor" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:827 +#: lib/who_need_help_web/live/request_live/show.ex:840 #, elixir-autogen, elixir-format msgid "3 — okay" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:826 +#: lib/who_need_help_web/live/request_live/show.ex:839 #, elixir-autogen, elixir-format msgid "4 — good" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:825 +#: lib/who_need_help_web/live/request_live/show.ex:838 #, elixir-autogen, elixir-format msgid "5 — excellent" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_controller.ex:118 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:120 #, elixir-autogen, elixir-format msgid "A link to confirm your email change has been sent to the new address." msgstr "" @@ -158,12 +158,12 @@ msgstr "" msgid "A signal requests human review; optional tracking absence is not proof of abuse." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:859 +#: lib/who_need_help_web/live/request_live/show.ex:872 #, elixir-autogen, elixir-format msgid "Accept only if you can complete this safely and for free." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:633 +#: lib/who_need_help_web/live/request_live/show.ex:638 #, elixir-autogen, elixir-format msgid "Accepted" msgstr "" @@ -184,7 +184,7 @@ msgstr "" msgid "Accounts" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:855 +#: lib/who_need_help_web/live/request_live/show.ex:868 #, elixir-autogen, elixir-format msgid "Action center" msgstr "" @@ -206,7 +206,7 @@ msgstr "" msgid "Activities are optional plans, not urgent help, and do not affect helper reputation. Meet in a suitable public place first. Organizers approve every participant; exact coordinates and group chat are limited to approved participants. Share route and safety plans for hikes, and never publish home addresses, access codes, or travel documents." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:163 +#: lib/who_need_help_web/live/activity_live/new.ex:168 #, elixir-autogen, elixir-format msgid "Activity details" msgstr "" @@ -217,12 +217,12 @@ msgid "Activity hidden." msgstr "" #: lib/who_need_help_web/live/activity_live/show.ex:18 -#: lib/who_need_help_web/live/activity_live/show.ex:177 +#: lib/who_need_help_web/live/activity_live/show.ex:181 #, elixir-autogen, elixir-format msgid "Activity is unavailable." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:36 +#: lib/who_need_help_web/live/activity_live/new.ex:41 #, elixir-autogen, elixir-format msgid "Activity published. You approve every participant." msgstr "" @@ -233,7 +233,7 @@ msgid "Activity restored." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:210 -#: lib/who_need_help_web/live/activity_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Activity type" msgstr "" @@ -264,7 +264,7 @@ msgstr "" msgid "Already registered?" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:603 +#: lib/who_need_help_web/live/request_live/show.ex:608 #, elixir-autogen, elixir-format msgid "Another helper already accepted this request." msgstr "" @@ -274,60 +274,60 @@ msgstr "" msgid "Any urgency" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:524 +#: lib/who_need_help_web/live/activity_live/show.ex:528 #, elixir-autogen, elixir-format msgid "Approval pending" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:574 +#: lib/who_need_help_web/live/activity_live/show.ex:581 #, elixir-autogen, elixir-format msgid "Approve" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:435 +#: lib/who_need_help_web/live/activity_live/show.ex:439 #, elixir-autogen, elixir-format msgid "Approved group chat" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:600 +#: lib/who_need_help_web/live/activity_live/show.ex:607 #, elixir-autogen, elixir-format msgid "Approved participants" msgstr "" #: lib/who_need_help_web/controllers/page_html/home.html.heex:51 -#: lib/who_need_help_web/live/activity_live/new.ex:258 -#: lib/who_need_help_web/live/request_live/new.ex:306 +#: lib/who_need_help_web/live/activity_live/new.ex:263 +#: lib/who_need_help_web/live/request_live/new.ex:311 #, elixir-autogen, elixir-format msgid "Approximate area" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Approximate public meeting area" msgstr "" #: lib/who_need_help_web/live/profile_live.ex:167 -#: lib/who_need_help_web/live/request_live/show.ex:639 +#: lib/who_need_help_web/live/request_live/show.ex:644 #, elixir-autogen, elixir-format msgid "Approximate publicly" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:314 +#: lib/who_need_help_web/live/request_live/new.ex:319 #, elixir-autogen, elixir-format msgid "Approximate publicly (recommended)" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:702 +#: lib/who_need_help_web/live/request_live/show.ex:707 #, elixir-autogen, elixir-format msgid "Area:" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:154 +#: lib/who_need_help_web/live/request_live/new.ex:159 #, elixir-autogen, elixir-format msgid "Ask for urgent help" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:92 +#: lib/who_need_help_web/live/category_proposal_live.ex:98 #, elixir-autogen, elixir-format msgid "Bicycle puncture" msgstr "" @@ -342,12 +342,12 @@ msgstr "" msgid "Block and report" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:641 +#: lib/who_need_help_web/live/activity_live/show.ex:648 #, elixir-autogen, elixir-format msgid "Block organizer" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1062 +#: lib/who_need_help_web/live/request_live/show.ex:1075 #, elixir-autogen, elixir-format msgid "Block this user" msgstr "" @@ -362,7 +362,7 @@ msgstr "" msgid "Both confirm and the helper enters a one-time code." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:118 +#: lib/who_need_help_web/live/request_live/new.ex:123 #, elixir-autogen, elixir-format msgid "Briefly describe the help you need" msgstr "" @@ -382,33 +382,33 @@ msgstr "" msgid "COMMUNITY TRUST" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:80 +#: lib/who_need_help_web/live/category_proposal_live.ex:81 #, elixir-autogen, elixir-format msgid "COMMUNITY-DRIVEN TAXONOMY" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:594 +#: lib/who_need_help_web/live/activity_live/show.ex:601 #, elixir-autogen, elixir-format msgid "Cancel activity" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:873 -#: lib/who_need_help_web/live/request_live/show.ex:967 +#: lib/who_need_help_web/live/request_live/show.ex:886 +#: lib/who_need_help_web/live/request_live/show.ex:980 #, elixir-autogen, elixir-format msgid "Cancel request" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:168 -#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/activity_live/show.ex:344 #: lib/who_need_help_web/live/request_live/index.ex:184 #: lib/who_need_help_web/live/request_live/index.ex:185 -#: lib/who_need_help_web/live/request_live/show.ex:636 +#: lib/who_need_help_web/live/request_live/show.ex:641 #, elixir-autogen, elixir-format msgid "Cancelled" msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:225 -#: lib/who_need_help_web/live/request_live/new.ex:180 +#: lib/who_need_help_web/live/request_live/new.ex:185 #, elixir-autogen, elixir-format msgid "Category" msgstr "" @@ -418,13 +418,13 @@ msgstr "" msgid "Category created and proposal approved." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:211 -#: lib/who_need_help_web/live/request_live/show.ex:691 +#: lib/who_need_help_web/live/request_live/new.ex:216 +#: lib/who_need_help_web/live/request_live/show.ex:696 #, elixir-autogen, elixir-format msgid "Category details" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:66 +#: lib/who_need_help_web/live/category_proposal_live.ex:67 #: lib/who_need_help_web/live/moderation_live.ex:466 #, elixir-autogen, elixir-format msgid "Category proposals" @@ -441,7 +441,7 @@ msgid "Change role" msgstr "" #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:21 -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:47 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:107 #, elixir-autogen, elixir-format msgid "Changing..." msgstr "" @@ -451,31 +451,31 @@ msgstr "" msgid "Choose a category, urgency, and safe location visibility." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:140 +#: lib/who_need_help_web/live/activity_live/new.ex:145 #, elixir-autogen, elixir-format msgid "Choose activity" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:181 +#: lib/who_need_help_web/live/request_live/new.ex:186 #, elixir-autogen, elixir-format msgid "Choose category" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:175 -#: lib/who_need_help_web/live/activity_live/new.ex:202 -#: lib/who_need_help_web/live/request_live/new.ex:223 -#: lib/who_need_help_web/live/request_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:180 +#: lib/who_need_help_web/live/activity_live/new.ex:207 +#: lib/who_need_help_web/live/request_live/new.ex:228 +#: lib/who_need_help_web/live/request_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Choose…" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:251 +#: lib/who_need_help_web/live/activity_live/new.ex:256 #, elixir-autogen, elixir-format msgid "City centre, near the main entrance" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:654 -#: lib/who_need_help_web/live/request_live/show.ex:1078 +#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/request_live/show.ex:1096 #, elixir-autogen, elixir-format msgid "Clear" msgstr "" @@ -485,7 +485,7 @@ msgstr "" msgid "Close" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:220 +#: lib/who_need_help_web/live/activity_live/new.ex:225 #, elixir-autogen, elixir-format msgid "Coffee and conversation" msgstr "" @@ -495,7 +495,7 @@ msgstr "" msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:835 +#: lib/who_need_help_web/live/request_live/show.ex:848 #, elixir-autogen, elixir-format msgid "Comment (optional)" msgstr "" @@ -506,19 +506,19 @@ msgid "Community helpers" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:262 -#: lib/who_need_help_web/live/activity_live/show.ex:392 -#: lib/who_need_help_web/live/activity_live/show.ex:546 -#: lib/who_need_help_web/live/activity_live/show.ex:603 +#: lib/who_need_help_web/live/activity_live/show.ex:396 +#: lib/who_need_help_web/live/activity_live/show.ex:553 +#: lib/who_need_help_web/live/activity_live/show.ex:610 #: lib/who_need_help_web/live/request_live/index.ex:287 #, elixir-autogen, elixir-format msgid "Community member" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:167 -#: lib/who_need_help_web/live/activity_live/show.ex:339 +#: lib/who_need_help_web/live/activity_live/show.ex:343 #: lib/who_need_help_web/live/request_live/index.ex:182 #: lib/who_need_help_web/live/request_live/index.ex:183 -#: lib/who_need_help_web/live/request_live/show.ex:635 +#: lib/who_need_help_web/live/request_live/show.ex:640 #, elixir-autogen, elixir-format msgid "Completed" msgstr "" @@ -533,24 +533,24 @@ msgstr "" msgid "Confirm and stay logged in" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:947 +#: lib/who_need_help_web/live/request_live/show.ex:960 #, elixir-autogen, elixir-format msgid "Confirm my part is complete" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:42 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:102 #, elixir-autogen, elixir-format msgid "Confirm new password" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:51 -#: lib/who_need_help_web/live/request_live/show.ex:611 +#: lib/who_need_help_web/live/category_proposal_live.ex:52 +#: lib/who_need_help_web/live/request_live/show.ex:616 #, elixir-autogen, elixir-format msgid "Confirm your account and ensure it is active first." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:100 -#: lib/who_need_help_web/live/request_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/request_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Confirm your email and ensure your account is active before publishing." msgstr "" @@ -573,17 +573,12 @@ msgid "Create a clear plan in a public place." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:188 -#: lib/who_need_help_web/live/activity_live/new.ex:11 +#: lib/who_need_help_web/live/activity_live/new.ex:12 #, elixir-autogen, elixir-format msgid "Create activity" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:59 -#, elixir-autogen, elixir-format -msgid "Create an account" -msgstr "" - -#: lib/who_need_help_web/live/activity_live/new.ex:115 +#: lib/who_need_help_web/live/activity_live/new.ex:120 #, elixir-autogen, elixir-format msgid "Create an activity" msgstr "" @@ -598,18 +593,13 @@ msgstr "" msgid "Create urgent request" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:58 -#, elixir-autogen, elixir-format -msgid "Creating account..." -msgstr "" - -#: lib/who_need_help_web/live/activity_live/show.ex:663 +#: lib/who_need_help_web/live/activity_live/show.ex:675 #, elixir-autogen, elixir-format msgid "Dangerous plan" msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:248 -#: lib/who_need_help_web/live/request_live/show.ex:1086 +#: lib/who_need_help_web/live/request_live/show.ex:1104 #, elixir-autogen, elixir-format msgid "Dangerous request" msgstr "" @@ -629,7 +619,7 @@ msgstr "" msgid "Decisions and access to reported chat evidence are written to the audit log." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:581 +#: lib/who_need_help_web/live/activity_live/show.ex:588 #, elixir-autogen, elixir-format msgid "Decline" msgstr "" @@ -639,7 +629,7 @@ msgstr "" msgid "Default location visibility" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:227 +#: lib/who_need_help_web/live/activity_live/new.ex:232 #, elixir-autogen, elixir-format msgid "Describe the public plan, approximate duration, and who might enjoy it." msgstr "" @@ -651,7 +641,7 @@ msgstr "" msgid "Dismissed" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:34 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -667,14 +657,14 @@ msgstr "" msgid "Don't have an account?" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:815 +#: lib/who_need_help_web/live/request_live/show.ex:820 #, elixir-autogen, elixir-format msgid "Double-blind review" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:25 -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 #: lib/who_need_help_web/controllers/user_session_html/new.html.heex:89 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:122 #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:15 #, elixir-autogen, elixir-format msgid "Email" @@ -711,7 +701,7 @@ msgstr "" msgid "English name" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:992 +#: lib/who_need_help_web/live/request_live/show.ex:1005 #, elixir-autogen, elixir-format msgid "Enter handover code" msgstr "" @@ -721,7 +711,7 @@ msgstr "" msgid "Exact address after matching" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:274 +#: lib/who_need_help_web/live/activity_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Exact coordinates are shown only to participants you approve." msgstr "" @@ -731,29 +721,29 @@ msgstr "" msgid "Exact for active match" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:316 -#: lib/who_need_help_web/live/request_live/show.ex:641 +#: lib/who_need_help_web/live/request_live/new.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:646 #, elixir-autogen, elixir-format msgid "Exact only for active match" msgstr "" #: lib/who_need_help_web/live/profile_live.ex:169 -#: lib/who_need_help_web/live/request_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:647 #, elixir-autogen, elixir-format msgid "Exact publicly" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:317 +#: lib/who_need_help_web/live/request_live/new.ex:322 #, elixir-autogen, elixir-format msgid "Exact publicly — explicit consent" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:710 +#: lib/who_need_help_web/live/request_live/show.ex:715 #, elixir-autogen, elixir-format msgid "Expires:" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:274 +#: lib/who_need_help_web/live/request_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Explain what is already arranged and what the volunteer needs to do." msgstr "" @@ -763,9 +753,9 @@ msgstr "" msgid "Find people to join" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:664 +#: lib/who_need_help_web/live/activity_live/show.ex:676 #: lib/who_need_help_web/live/moderation_live.ex:250 -#: lib/who_need_help_web/live/request_live/show.ex:1088 +#: lib/who_need_help_web/live/request_live/show.ex:1106 #, elixir-autogen, elixir-format msgid "Fraud" msgstr "" @@ -785,7 +775,7 @@ msgstr "" msgid "GitHub verification is unavailable until the operator configures its OAuth credentials. No manually entered link is treated as verified." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:975 +#: lib/who_need_help_web/live/request_live/show.ex:988 #, elixir-autogen, elixir-format msgid "HANDOVER CODE FOR THE HELPER" msgstr "" @@ -800,9 +790,9 @@ msgstr "" msgid "Handover code verified." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:662 +#: lib/who_need_help_web/live/activity_live/show.ex:674 #: lib/who_need_help_web/live/moderation_live.ex:249 -#: lib/who_need_help_web/live/request_live/show.ex:1087 +#: lib/who_need_help_web/live/request_live/show.ex:1105 #, elixir-autogen, elixir-format msgid "Harassment" msgstr "" @@ -822,7 +812,7 @@ msgstr "" msgid "Helper" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:878 +#: lib/who_need_help_web/live/request_live/show.ex:891 #, elixir-autogen, elixir-format msgid "Helper: %{name}" msgstr "" @@ -852,10 +842,10 @@ msgstr "" msgid "Hi %{email},\n\nYou can log into your account by visiting the URL below:\n\n%{url}\n\nIf you didn't request this email, please ignore this." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:259 +#: lib/who_need_help_web/live/activity_live/new.ex:264 #: lib/who_need_help_web/live/profile_live.ex:166 -#: lib/who_need_help_web/live/request_live/new.ex:315 -#: lib/who_need_help_web/live/request_live/show.ex:640 +#: lib/who_need_help_web/live/request_live/new.ex:320 +#: lib/who_need_help_web/live/request_live/show.ex:645 #, elixir-autogen, elixir-format msgid "Hidden" msgstr "" @@ -881,33 +871,34 @@ msgstr "" msgid "Human decisions retained" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:48 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:39 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:85 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "" #: lib/who_need_help_web/controllers/page_html/home.html.heex:56 -#: lib/who_need_help_web/live/request_live/show.ex:862 +#: lib/who_need_help_web/live/request_live/show.ex:875 #, elixir-autogen, elixir-format msgid "I can help" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:127 +#: lib/who_need_help_web/live/request_live/new.ex:132 #, elixir-autogen, elixir-format msgid "I confirm there is no immediate danger, people and vehicles are away from active traffic or emergency services have already been contacted, and the requested task is lawful." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:121 +#: lib/who_need_help_web/live/request_live/new.ex:126 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, medical advice request, controlled substance, or request to buy medicine on my behalf." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:133 +#: lib/who_need_help_web/live/request_live/new.ex:138 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, there is no immediate danger, and the requested task is lawful." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:298 +#: lib/who_need_help_web/live/activity_live/new.ex:303 #, elixir-autogen, elixir-format msgid "I will approve participants individually, keep sensitive details in the approved group, and use an appropriate public first meeting point." msgstr "" @@ -922,21 +913,21 @@ msgstr "" msgid "If your email is in our system, you will receive instructions for logging in shortly." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/activity_live/show.ex:678 #: lib/who_need_help_web/live/moderation_live.ex:253 -#: lib/who_need_help_web/live/request_live/show.ex:1091 +#: lib/who_need_help_web/live/request_live/show.ex:1109 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1027 +#: lib/who_need_help_web/live/request_live/show.ex:1040 #, elixir-autogen, elixir-format msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops." msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:180 #: lib/who_need_help_web/live/request_live/index.ex:181 -#: lib/who_need_help_web/live/request_live/show.ex:634 +#: lib/who_need_help_web/live/request_live/show.ex:639 #, elixir-autogen, elixir-format msgid "In progress" msgstr "" @@ -956,49 +947,49 @@ msgstr "" msgid "Invalid email or password" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:314 +#: lib/who_need_help_web/live/activity_live/show.ex:318 #, elixir-autogen, elixir-format msgid "Join requests are closed." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:380 +#: lib/who_need_help_web/live/activity_live/show.ex:384 #, elixir-autogen, elixir-format msgid "Join requests close" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:239 +#: lib/who_need_help_web/live/activity_live/new.ex:244 #, elixir-autogen, elixir-format msgid "Join requests close (UTC)" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:282 -#: lib/who_need_help_web/live/request_live/new.ex:340 +#: lib/who_need_help_web/live/activity_live/new.ex:287 +#: lib/who_need_help_web/live/request_live/new.ex:345 #, elixir-autogen, elixir-format msgid "Latitude" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:534 +#: lib/who_need_help_web/live/activity_live/show.ex:541 #, elixir-autogen, elixir-format msgid "Leave activity" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1025 +#: lib/who_need_help_web/live/request_live/show.ex:1038 #, elixir-autogen, elixir-format msgid "Live location" msgstr "" #: lib/who_need_help_web/controllers/page_html/safety.html.heex:51 -#: lib/who_need_help_web/live/activity_live/show.ex:503 +#: lib/who_need_help_web/live/activity_live/show.ex:507 #, elixir-autogen, elixir-format msgid "Location privacy" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:914 +#: lib/who_need_help_web/live/request_live/show.ex:927 #, elixir-autogen, elixir-format msgid "Location sharing is optional; missing evidence is not proof of abuse." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:312 +#: lib/who_need_help_web/live/request_live/new.ex:317 #, elixir-autogen, elixir-format msgid "Location visibility" msgstr "" @@ -1008,12 +999,12 @@ msgstr "" msgid "Location-supported people" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:717 +#: lib/who_need_help_web/live/request_live/show.ex:722 #, elixir-autogen, elixir-format msgid "Location:" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:102 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:136 #, elixir-autogen, elixir-format msgid "Log in and stay logged in" msgstr "" @@ -1023,23 +1014,18 @@ msgstr "" msgid "Log in instructions" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:139 #, elixir-autogen, elixir-format msgid "Log in only this time" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 -#, elixir-autogen, elixir-format -msgid "Log in with email" -msgstr "" - #: lib/who_need_help_web/controllers/user_session_controller.ex:117 #, elixir-autogen, elixir-format msgid "Logged out successfully." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:289 -#: lib/who_need_help_web/live/request_live/new.ex:347 +#: lib/who_need_help_web/live/activity_live/new.ex:294 +#: lib/who_need_help_web/live/request_live/new.ex:352 #, elixir-autogen, elixir-format msgid "Longitude" msgstr "" @@ -1059,7 +1045,7 @@ msgstr "" msgid "Manually added links are public and marked unverified. A verified badge is only granted after completing a provider authorization flow." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:591 +#: lib/who_need_help_web/live/activity_live/show.ex:598 #, elixir-autogen, elixir-format msgid "Mark completed" msgstr "" @@ -1071,12 +1057,12 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:178 #: lib/who_need_help_web/live/request_live/index.ex:179 -#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:637 #, elixir-autogen, elixir-format msgid "Matched" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:113 +#: lib/who_need_help_web/live/request_live/new.ex:118 #, elixir-autogen, elixir-format msgid "Medicine is ready at the pharmacy" msgstr "" @@ -1091,7 +1077,7 @@ msgstr "" msgid "Medicine pickup" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:156 +#: lib/who_need_help_web/live/request_live/new.ex:161 #, elixir-autogen, elixir-format msgid "Medicine pickup remains the first priority. Roadside categories use the same moderated, extensible request flow with details specific to each type of help." msgstr "" @@ -1106,7 +1092,7 @@ msgstr "" msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:386 +#: lib/who_need_help_web/live/activity_live/show.ex:390 #, elixir-autogen, elixir-format msgid "Meeting area" msgstr "" @@ -1126,12 +1112,12 @@ msgstr "" msgid "Merge note" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:483 +#: lib/who_need_help_web/live/activity_live/show.ex:487 #, elixir-autogen, elixir-format msgid "Message the approved group" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:97 +#: lib/who_need_help_web/live/category_proposal_live.ex:103 #, elixir-autogen, elixir-format msgid "Mode" msgstr "" @@ -1165,7 +1151,7 @@ msgstr "" msgid "Nearby help" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:116 +#: lib/who_need_help_web/live/request_live/new.ex:121 #, elixir-autogen, elixir-format msgid "Need roadside help in a safe location" msgstr "" @@ -1175,15 +1161,15 @@ msgstr "" msgid "Network" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:94 #, elixir-autogen, elixir-format msgid "New password" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:207 -#: lib/who_need_help_web/live/activity_live/show.ex:296 -#: lib/who_need_help_web/live/request_live/new.ex:255 -#: lib/who_need_help_web/live/request_live/show.ex:563 +#: lib/who_need_help_web/live/activity_live/new.ex:212 +#: lib/who_need_help_web/live/activity_live/show.ex:300 +#: lib/who_need_help_web/live/request_live/new.ex:260 +#: lib/who_need_help_web/live/request_live/show.ex:568 #, elixir-autogen, elixir-format msgid "No" msgstr "" @@ -1193,8 +1179,8 @@ msgstr "" msgid "No completed help yet." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:451 -#: lib/who_need_help_web/live/request_live/show.ex:771 +#: lib/who_need_help_web/live/activity_live/show.ex:455 +#: lib/who_need_help_web/live/request_live/show.ex:776 #, elixir-autogen, elixir-format msgid "No messages yet." msgstr "" @@ -1214,12 +1200,12 @@ msgstr "" msgid "No open signals." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:586 +#: lib/who_need_help_web/live/activity_live/show.ex:593 #, elixir-autogen, elixir-format msgid "No pending join requests." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:134 +#: lib/who_need_help_web/live/category_proposal_live.ex:140 #, elixir-autogen, elixir-format msgid "No proposals yet." msgstr "" @@ -1252,9 +1238,9 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:168 #: lib/who_need_help_web/live/request_live/index.ex:169 #: lib/who_need_help_web/live/request_live/index.ex:243 -#: lib/who_need_help_web/live/request_live/new.ex:293 -#: lib/who_need_help_web/live/request_live/show.ex:623 -#: lib/who_need_help_web/live/request_live/show.ex:624 +#: lib/who_need_help_web/live/request_live/new.ex:298 +#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/show.ex:629 #, elixir-autogen, elixir-format msgid "Now" msgstr "" @@ -1279,17 +1265,17 @@ msgstr "" msgid "Only the conversation linked to this report is displayed." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:437 +#: lib/who_need_help_web/live/activity_live/show.ex:441 #, elixir-autogen, elixir-format msgid "Only the organizer and approved participants can read or send these messages." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:166 -#: lib/who_need_help_web/live/activity_live/show.ex:338 +#: lib/who_need_help_web/live/activity_live/show.ex:342 #: lib/who_need_help_web/live/moderation_live.ex:257 #: lib/who_need_help_web/live/request_live/index.ex:176 #: lib/who_need_help_web/live/request_live/index.ex:177 -#: lib/who_need_help_web/live/request_live/show.ex:631 +#: lib/who_need_help_web/live/request_live/show.ex:636 #, elixir-autogen, elixir-format msgid "Open" msgstr "" @@ -1299,7 +1285,7 @@ msgstr "" msgid "Open abuse signals" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:128 +#: lib/who_need_help_web/live/category_proposal_live.ex:134 #, elixir-autogen, elixir-format msgid "Open proposals" msgstr "" @@ -1309,50 +1295,50 @@ msgstr "" msgid "Optional external thank-you link" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1011 +#: lib/who_need_help_web/live/request_live/show.ex:1024 #, elixir-autogen, elixir-format msgid "Optional thank-you link" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:390 +#: lib/who_need_help_web/live/activity_live/show.ex:394 #, elixir-autogen, elixir-format msgid "Organizer" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:155 +#: lib/who_need_help_web/live/activity_live/show.ex:159 #, elixir-autogen, elixir-format msgid "Organizer blocked. Their activities and messages are hidden." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:539 +#: lib/who_need_help_web/live/activity_live/show.ex:546 #, elixir-autogen, elixir-format msgid "Organizer controls" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:399 +#: lib/who_need_help_web/live/activity_live/show.ex:403 #, elixir-autogen, elixir-format msgid "Organizer social links" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:667 +#: lib/who_need_help_web/live/activity_live/show.ex:679 #: lib/who_need_help_web/live/moderation_live.ex:254 #: lib/who_need_help_web/live/profile_live.ex:311 -#: lib/who_need_help_web/live/request_live/show.ex:1092 +#: lib/who_need_help_web/live/request_live/show.ex:1110 #, elixir-autogen, elixir-format msgid "Other" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:112 #, elixir-autogen, elixir-format msgid "Parent (optional)" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:459 +#: lib/who_need_help_web/live/activity_live/show.ex:463 #, elixir-autogen, elixir-format msgid "Participant" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:97 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:130 #, elixir-autogen, elixir-format msgid "Password" msgstr "" @@ -1362,27 +1348,27 @@ msgstr "" msgid "Password updated successfully." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:280 +#: lib/who_need_help_web/live/request_live/new.ex:285 #, elixir-autogen, elixir-format msgid "Pickup instructions (optional)" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:686 +#: lib/who_need_help_web/live/request_live/show.ex:691 #, elixir-autogen, elixir-format msgid "Pickup notes" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:225 +#: lib/who_need_help_web/live/activity_live/new.ex:230 #, elixir-autogen, elixir-format msgid "Plan and expectations" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:421 +#: lib/who_need_help_web/live/activity_live/show.ex:425 #, elixir-autogen, elixir-format msgid "Plan details" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:619 +#: lib/who_need_help_web/live/request_live/show.ex:624 #, elixir-autogen, elixir-format msgid "Please check the entered data." msgstr "" @@ -1392,12 +1378,12 @@ msgstr "" msgid "Please check the submitted fields." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:334 +#: lib/who_need_help_web/live/activity_live/show.ex:338 #, elixir-autogen, elixir-format msgid "Please check the submitted message." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:307 +#: lib/who_need_help_web/live/request_live/new.ex:312 #, elixir-autogen, elixir-format msgid "Podil, near the central pharmacy" msgstr "" @@ -1413,7 +1399,7 @@ msgstr "" msgid "Primary navigation" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:755 +#: lib/who_need_help_web/live/request_live/show.ex:760 #, elixir-autogen, elixir-format msgid "Private match chat" msgstr "" @@ -1429,7 +1415,7 @@ msgid "Profile updated." msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:251 -#: lib/who_need_help_web/live/request_live/show.ex:1089 +#: lib/who_need_help_web/live/request_live/show.ex:1107 #, elixir-autogen, elixir-format msgid "Prohibited item" msgstr "" @@ -1449,12 +1435,12 @@ msgstr "" msgid "Proposal rejected." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:86 +#: lib/who_need_help_web/live/category_proposal_live.ex:87 #, elixir-autogen, elixir-format msgid "Proposals and votes guide moderators. Approval remains a human decision." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:91 +#: lib/who_need_help_web/live/category_proposal_live.ex:97 #, elixir-autogen, elixir-format msgid "Proposed category" msgstr "" @@ -1464,7 +1450,7 @@ msgstr "" msgid "Public location can be hidden, approximate, or explicitly exact. Live tracking is optional. A browser shares only while its page is open; Android shows a persistent foreground-service notification while sharing. The current exact point is deleted when sharing stops, a match ends, or either participant blocks the other; derived movement and proximity evidence may remain." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:256 +#: lib/who_need_help_web/live/activity_live/new.ex:261 #, elixir-autogen, elixir-format msgid "Public map visibility" msgstr "" @@ -1479,28 +1465,28 @@ msgstr "" msgid "Public reputation" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:117 +#: lib/who_need_help_web/live/activity_live/new.ex:122 #, elixir-autogen, elixir-format msgid "Publish a clear public plan. Join requests stay pending until you approve them." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:310 +#: lib/who_need_help_web/live/activity_live/new.ex:315 #, elixir-autogen, elixir-format msgid "Publish activity" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:123 +#: lib/who_need_help_web/live/category_proposal_live.ex:129 #, elixir-autogen, elixir-format msgid "Publish proposal" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:364 +#: lib/who_need_help_web/live/request_live/new.ex:369 #, elixir-autogen, elixir-format msgid "Publish request" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:308 -#: lib/who_need_help_web/live/request_live/new.ex:362 +#: lib/who_need_help_web/live/activity_live/new.ex:313 +#: lib/who_need_help_web/live/request_live/new.ex:367 #, elixir-autogen, elixir-format msgid "Publishing…" msgstr "" @@ -1526,17 +1512,17 @@ msgid "Ranking prioritizes unique people helped with optional location-supported msgstr "" #: lib/who_need_help_web/live/leaderboard_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:823 +#: lib/who_need_help_web/live/request_live/show.ex:836 #, elixir-autogen, elixir-format msgid "Rating" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:40 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 #, elixir-autogen, elixir-format msgid "Read the" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:660 +#: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" msgstr "" @@ -1561,22 +1547,22 @@ msgstr "" msgid "Remove" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:468 +#: lib/who_need_help_web/live/activity_live/show.ex:472 #, elixir-autogen, elixir-format msgid "Report" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:789 +#: lib/who_need_help_web/live/request_live/show.ex:794 #, elixir-autogen, elixir-format msgid "Report message" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1084 +#: lib/who_need_help_web/live/request_live/show.ex:1102 #, elixir-autogen, elixir-format msgid "Report reason" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:123 +#: lib/who_need_help_web/live/activity_live/show.ex:126 #: lib/who_need_help_web/live/request_live/show.ex:261 #, elixir-autogen, elixir-format msgid "Report sent to moderators." @@ -1597,7 +1583,7 @@ msgstr "" msgid "Reports" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1053 +#: lib/who_need_help_web/live/request_live/show.ex:1066 #, elixir-autogen, elixir-format msgid "Reports go to moderators. Blocking hides requests in both directions and prevents new chat messages." msgstr "" @@ -1607,7 +1593,7 @@ msgstr "" msgid "Request cancelled." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:301 +#: lib/who_need_help_web/live/request_live/new.ex:306 #, elixir-autogen, elixir-format msgid "Request expires (UTC)" msgstr "" @@ -1622,22 +1608,22 @@ msgstr "" msgid "Request restored." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:521 +#: lib/who_need_help_web/live/activity_live/show.ex:525 #, elixir-autogen, elixir-format msgid "Request to join" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:725 +#: lib/who_need_help_web/live/request_live/show.ex:730 #, elixir-autogen, elixir-format msgid "Requester links:" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:706 +#: lib/who_need_help_web/live/request_live/show.ex:711 #, elixir-autogen, elixir-format msgid "Requester:" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:745 #, elixir-autogen, elixir-format msgid "Requester: %{count} completed" msgstr "" @@ -1665,6 +1651,7 @@ msgid "Review note" msgstr "" #: lib/who_need_help_web/live/request_live/show.ex:233 +#: lib/who_need_help_web/live/request_live/show.ex:825 #, elixir-autogen, elixir-format msgid "Review saved. It appears after both participants review." msgstr "" @@ -1696,8 +1683,8 @@ msgstr "" msgid "Russian name (optional)" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:635 -#: lib/who_need_help_web/live/request_live/show.ex:1051 +#: lib/who_need_help_web/live/activity_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:1064 #, elixir-autogen, elixir-format msgid "Safety controls" msgstr "" @@ -1720,7 +1707,7 @@ msgstr "" msgid "Save" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:48 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:108 #, elixir-autogen, elixir-format msgid "Save Password" msgstr "" @@ -1733,21 +1720,21 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:172 #: lib/who_need_help_web/live/request_live/index.ex:173 #: lib/who_need_help_web/live/request_live/index.ex:245 -#: lib/who_need_help_web/live/request_live/new.ex:295 -#: lib/who_need_help_web/live/request_live/show.ex:627 -#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/new.ex:300 +#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:633 #, elixir-autogen, elixir-format msgid "Scheduled" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:486 -#: lib/who_need_help_web/live/request_live/show.ex:806 +#: lib/who_need_help_web/live/activity_live/show.ex:490 +#: lib/who_need_help_web/live/request_live/show.ex:811 #, elixir-autogen, elixir-format msgid "Send" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:676 -#: lib/who_need_help_web/live/request_live/show.ex:1101 +#: lib/who_need_help_web/live/activity_live/show.ex:688 +#: lib/who_need_help_web/live/request_live/show.ex:1119 #, elixir-autogen, elixir-format msgid "Send report" msgstr "" @@ -1757,12 +1744,12 @@ msgstr "" msgid "Set CODEX_SESSION_ID in the deployment environment before the submission recording." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1038 +#: lib/who_need_help_web/live/request_live/show.ex:1051 #, elixir-autogen, elixir-format msgid "Share location" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:282 +#: lib/who_need_help_web/live/request_live/new.ex:287 #, elixir-autogen, elixir-format msgid "Share only non-sensitive instructions. Exact details can wait for the matched chat." msgstr "" @@ -1772,8 +1759,8 @@ msgstr "" msgid "Short bio" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:219 -#: lib/who_need_help_web/live/request_live/new.ex:266 +#: lib/who_need_help_web/live/activity_live/new.ex:224 +#: lib/who_need_help_web/live/request_live/new.ex:271 #, elixir-autogen, elixir-format msgid "Short title" msgstr "" @@ -1803,8 +1790,8 @@ msgstr "" msgid "Social activities" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:100 -#: lib/who_need_help_web/live/category_proposal_live.ex:143 +#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:149 #, elixir-autogen, elixir-format msgid "Social activity" msgstr "" @@ -1844,24 +1831,24 @@ msgstr "" msgid "Social verification session is invalid." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:665 +#: lib/who_need_help_web/live/activity_live/show.ex:677 #: lib/who_need_help_web/live/moderation_live.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:1090 +#: lib/who_need_help_web/live/request_live/show.ex:1108 #, elixir-autogen, elixir-format msgid "Spam" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:940 +#: lib/who_need_help_web/live/request_live/show.ex:953 #, elixir-autogen, elixir-format msgid "Start helping" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:374 +#: lib/who_need_help_web/live/activity_live/show.ex:378 #, elixir-autogen, elixir-format msgid "Starts" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:234 +#: lib/who_need_help_web/live/activity_live/new.ex:239 #, elixir-autogen, elixir-format msgid "Starts at (UTC)" msgstr "" @@ -1871,7 +1858,7 @@ msgstr "" msgid "Status" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1046 +#: lib/who_need_help_web/live/request_live/show.ex:1059 #, elixir-autogen, elixir-format msgid "Stop and delete position" msgstr "" @@ -1886,7 +1873,7 @@ msgstr "" msgid "Structured fields must be a valid JSON object." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:837 +#: lib/who_need_help_web/live/request_live/show.ex:850 #, elixir-autogen, elixir-format msgid "Submit review" msgstr "" @@ -1897,13 +1884,13 @@ msgstr "" msgid "Suspended" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:604 +#: lib/who_need_help_web/live/request_live/show.ex:609 #, elixir-autogen, elixir-format msgid "That handover code is not correct." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:140 -#: lib/who_need_help_web/live/request_live/show.ex:278 +#: lib/who_need_help_web/live/activity_live/show.ex:144 +#: lib/who_need_help_web/live/request_live/show.ex:279 #, elixir-autogen, elixir-format msgid "That message cannot be reported from this view." msgstr "" @@ -1913,7 +1900,7 @@ msgstr "" msgid "That social account is already linked." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:608 +#: lib/who_need_help_web/live/request_live/show.ex:613 #, elixir-autogen, elixir-format msgid "That step is not available in the current state." msgstr "" @@ -1928,7 +1915,7 @@ msgstr "" msgid "That verification provider is not supported." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:312 +#: lib/who_need_help_web/live/activity_live/show.ex:316 #, elixir-autogen, elixir-format msgid "The approved group has reached its capacity." msgstr "" @@ -1948,7 +1935,7 @@ msgstr "" msgid "The link is invalid or it has expired." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:526 +#: lib/who_need_help_web/live/activity_live/show.ex:530 #, elixir-autogen, elixir-format msgid "The organizer has not approved your request yet." msgstr "" @@ -1963,48 +1950,48 @@ msgstr "" msgid "The platform never handles, requires, splits, or guarantees a payment." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:508 +#: lib/who_need_help_web/live/activity_live/show.ex:512 #, elixir-autogen, elixir-format msgid "The public map is approximate or hidden. Exact coordinates appear only after approval." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:648 +#: lib/who_need_help_web/live/activity_live/show.ex:660 #, elixir-autogen, elixir-format msgid "The report is scoped to the selected group message." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1076 +#: lib/who_need_help_web/live/request_live/show.ex:1094 #, elixir-autogen, elixir-format msgid "The report will be scoped to the selected chat message." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:315 +#: lib/who_need_help_web/live/activity_live/show.ex:319 #, elixir-autogen, elixir-format msgid "This action is unavailable." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:322 +#: lib/who_need_help_web/live/activity_live/show.ex:326 #, elixir-autogen, elixir-format msgid "This activity is no longer open." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:320 +#: lib/who_need_help_web/live/activity_live/show.ex:324 #, elixir-autogen, elixir-format msgid "This activity state has already changed." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:613 +#: lib/who_need_help_web/live/request_live/show.ex:618 #, elixir-autogen, elixir-format msgid "This interaction is blocked." msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:210 -#: lib/who_need_help_web/live/request_live/show.ex:665 +#: lib/who_need_help_web/live/request_live/show.ex:670 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:164 +#: lib/who_need_help_web/live/request_live/new.ex:169 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger. Never include prescription details, payment card data, access codes, or other sensitive information." msgstr "" @@ -2014,7 +2001,7 @@ msgstr "" msgid "This proposal has already been reviewed." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:56 +#: lib/who_need_help_web/live/category_proposal_live.ex:57 #, elixir-autogen, elixir-format msgid "This proposal is already closed." msgstr "" @@ -2024,13 +2011,13 @@ msgstr "" msgid "This report has no linked conversation." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1016 +#: lib/who_need_help_web/live/request_live/show.ex:1029 #, elixir-autogen, elixir-format msgid "This request already has a helper." msgstr "" #: lib/who_need_help_web/live/request_live/show.ex:74 -#: lib/who_need_help_web/live/request_live/show.ex:615 +#: lib/who_need_help_web/live/request_live/show.ex:620 #, elixir-autogen, elixir-format msgid "This request is no longer available." msgstr "" @@ -2048,28 +2035,29 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:170 #: lib/who_need_help_web/live/request_live/index.ex:171 #: lib/who_need_help_web/live/request_live/index.ex:244 -#: lib/who_need_help_web/live/request_live/new.ex:294 -#: lib/who_need_help_web/live/request_live/show.ex:625 -#: lib/who_need_help_web/live/request_live/show.ex:626 +#: lib/who_need_help_web/live/request_live/new.ex:299 +#: lib/who_need_help_web/live/request_live/show.ex:630 +#: lib/who_need_help_web/live/request_live/show.ex:631 #, elixir-autogen, elixir-format msgid "Today" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:103 -#: lib/who_need_help_web/live/activity_live/show.ex:332 -#: lib/who_need_help_web/live/category_proposal_live.ex:54 +#: lib/who_need_help_web/live/activity_live/new.ex:108 +#: lib/who_need_help_web/live/activity_live/show.ex:336 +#: lib/who_need_help_web/live/category_proposal_live.ex:55 #: lib/who_need_help_web/live/profile_live.ex:59 -#: lib/who_need_help_web/live/request_live/show.ex:618 +#: lib/who_need_help_web/live/request_live/show.ex:623 #, elixir-autogen, elixir-format msgid "Too many actions in the configured time window." msgstr "" +#: lib/who_need_help_web/controllers/google_auth_controller.ex:180 #: lib/who_need_help_web/controllers/user_registration_controller.ex:49 #, elixir-autogen, elixir-format msgid "Too many registration attempts in the configured time window." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:142 +#: lib/who_need_help_web/live/request_live/new.ex:147 #, elixir-autogen, elixir-format msgid "Too many requests in the configured time window." msgstr "" @@ -2079,7 +2067,7 @@ msgstr "" msgid "Too many sign-in emails in the configured time window." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:107 +#: lib/who_need_help_web/live/category_proposal_live.ex:113 #, elixir-autogen, elixir-format msgid "Top level" msgstr "" @@ -2089,7 +2077,7 @@ msgstr "" msgid "Total" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:246 +#: lib/who_need_help_web/live/activity_live/new.ex:251 #, elixir-autogen, elixir-format msgid "Total group capacity" msgstr "" @@ -2109,7 +2097,7 @@ msgstr "" msgid "Unblock" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1069 +#: lib/who_need_help_web/live/request_live/show.ex:1082 #, elixir-autogen, elixir-format msgid "Unblock this user" msgstr "" @@ -2125,18 +2113,18 @@ msgid "Update email instructions" msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:240 -#: lib/who_need_help_web/live/request_live/new.ex:291 +#: lib/who_need_help_web/live/request_live/new.ex:296 #, elixir-autogen, elixir-format msgid "Urgency" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:99 -#: lib/who_need_help_web/live/category_proposal_live.ex:142 +#: lib/who_need_help_web/live/category_proposal_live.ex:105 +#: lib/who_need_help_web/live/category_proposal_live.ex:148 #, elixir-autogen, elixir-format msgid "Urgent help" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:123 +#: lib/who_need_help_web/live/activity_live/new.ex:128 #, elixir-autogen, elixir-format msgid "Use a public first meeting point. Do not publish a home address, access code, travel document, or other sensitive information." msgstr "" @@ -2151,8 +2139,8 @@ msgstr "" msgid "Use light theme" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:271 -#: lib/who_need_help_web/live/request_live/new.ex:329 +#: lib/who_need_help_web/live/activity_live/new.ex:276 +#: lib/who_need_help_web/live/request_live/new.ex:334 #, elixir-autogen, elixir-format msgid "Use my current foreground location" msgstr "" @@ -2174,7 +2162,7 @@ msgstr "" msgid "User" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:307 +#: lib/who_need_help_web/live/request_live/show.ex:308 #, elixir-autogen, elixir-format msgid "User blocked. Their requests and new messages are hidden." msgstr "" @@ -2195,7 +2183,7 @@ msgid "User status updated." msgstr "" #: lib/who_need_help_web/live/profile_live.ex:87 -#: lib/who_need_help_web/live/request_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:322 #, elixir-autogen, elixir-format msgid "User unblocked." msgstr "" @@ -2210,7 +2198,7 @@ msgstr "" msgid "Verify GitHub" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:996 +#: lib/who_need_help_web/live/request_live/show.ex:1009 #, elixir-autogen, elixir-format msgid "Verify handover" msgstr "" @@ -2230,11 +2218,12 @@ msgstr "" msgid "Visible reviews" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:866 +#: lib/who_need_help_web/live/request_live/show.ex:879 #, elixir-autogen, elixir-format msgid "Waiting for a nearby volunteer." msgstr "" +#: lib/who_need_help_web/controllers/google_auth_controller.ex:137 #: lib/who_need_help_web/controllers/user_session_controller.ex:26 #: lib/who_need_help_web/controllers/user_session_controller.ex:51 #, elixir-autogen, elixir-format @@ -2246,18 +2235,18 @@ msgstr "" msgid "What Codex contributed" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:673 -#: lib/who_need_help_web/live/request_live/show.ex:1098 +#: lib/who_need_help_web/live/activity_live/show.ex:685 +#: lib/who_need_help_web/live/request_live/show.ex:1116 #, elixir-autogen, elixir-format msgid "What happened?" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:83 +#: lib/who_need_help_web/live/category_proposal_live.ex:84 #, elixir-autogen, elixir-format msgid "What help category is missing?" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:272 +#: lib/who_need_help_web/live/request_live/new.ex:277 #, elixir-autogen, elixir-format msgid "What help do you need?" msgstr "" @@ -2277,7 +2266,7 @@ msgstr "" msgid "Who Need Help home" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:53 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "Who Need Help is not an emergency or medical service. Medicine requests are limited to pickup of legal items already purchased or reserved." msgstr "" @@ -2292,40 +2281,40 @@ msgstr "" msgid "Who needs help?" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:121 +#: lib/who_need_help_web/live/category_proposal_live.ex:127 #, elixir-autogen, elixir-format msgid "Why is this useful?" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:957 +#: lib/who_need_help_web/live/request_live/show.ex:970 #, elixir-autogen, elixir-format msgid "Withdraw from this request" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:802 +#: lib/who_need_help_web/live/request_live/show.ex:807 #, elixir-autogen, elixir-format msgid "Write a safe coordination message…" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:204 -#: lib/who_need_help_web/live/activity_live/show.ex:294 -#: lib/who_need_help_web/live/request_live/new.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:561 +#: lib/who_need_help_web/live/activity_live/new.ex:209 +#: lib/who_need_help_web/live/activity_live/show.ex:298 +#: lib/who_need_help_web/live/request_live/new.ex:257 +#: lib/who_need_help_web/live/request_live/show.ex:566 #, elixir-autogen, elixir-format msgid "Yes" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:309 +#: lib/who_need_help_web/live/activity_live/show.ex:313 #, elixir-autogen, elixir-format msgid "You already have a join request for this activity." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:58 +#: lib/who_need_help_web/live/category_proposal_live.ex:59 #, elixir-autogen, elixir-format msgid "You already voted for this proposal." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:506 +#: lib/who_need_help_web/live/activity_live/show.ex:510 #, elixir-autogen, elixir-format msgid "You are approved, so the map can show the exact meeting coordinates." msgstr "" @@ -2335,8 +2324,8 @@ msgstr "" msgid "You are matched. Use chat to coordinate safely." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:317 -#: lib/who_need_help_web/live/request_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:610 #, elixir-autogen, elixir-format msgid "You are not allowed to do that." msgstr "" @@ -2346,7 +2335,7 @@ msgstr "" msgid "You are running the local mail adapter." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:332 +#: lib/who_need_help_web/live/request_live/new.ex:337 #, elixir-autogen, elixir-format msgid "You can also enter coordinates manually for local testing." msgstr "" @@ -2356,12 +2345,12 @@ msgstr "" msgid "You can be the first person to ask the community." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:602 +#: lib/who_need_help_web/live/request_live/show.ex:607 #, elixir-autogen, elixir-format msgid "You cannot accept your own request." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:316 +#: lib/who_need_help_web/live/activity_live/show.ex:320 #, elixir-autogen, elixir-format msgid "You cannot report your own content." msgstr "" @@ -2413,7 +2402,7 @@ msgstr "" msgid "Your profile" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:47 +#: lib/who_need_help_web/live/request_live/new.ex:52 #, elixir-autogen, elixir-format msgid "Your request is now visible to nearby helpers." msgstr "" @@ -2423,7 +2412,7 @@ msgstr "" msgid "Your requests" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:817 +#: lib/who_need_help_web/live/request_live/show.ex:822 #, elixir-autogen, elixir-format msgid "Your review is revealed only after both participants submit." msgstr "" @@ -2443,13 +2432,13 @@ msgstr "" msgid "activity message" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:42 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:261 -#: lib/who_need_help_web/live/category_proposal_live.ex:147 +#: lib/who_need_help_web/live/category_proposal_live.ex:153 #: lib/who_need_help_web/live/moderation_live.ex:317 #: lib/who_need_help_web/live/request_live/index.ex:286 #, elixir-autogen, elixir-format @@ -2458,7 +2447,7 @@ msgstr "" #: lib/who_need_help_web/live/profile_live.ex:189 #: lib/who_need_help_web/live/request_live/index.ex:323 -#: lib/who_need_help_web/live/request_live/show.ex:883 +#: lib/who_need_help_web/live/request_live/show.ex:896 #, elixir-autogen, elixir-format msgid "completed" msgstr "" @@ -2473,59 +2462,54 @@ msgstr "" msgid "for an account now." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:910 +#: lib/who_need_help_web/live/request_live/show.ex:923 #, elixir-autogen, elixir-format msgid "helper movement supported" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:911 +#: lib/who_need_help_web/live/request_live/show.ex:924 #, elixir-autogen, elixir-format msgid "no movement evidence" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:902 +#: lib/who_need_help_web/live/request_live/show.ex:915 #, elixir-autogen, elixir-format msgid "no proximity evidence" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 -#, elixir-autogen, elixir-format -msgid "or" -msgstr "" - -#: lib/who_need_help_web/live/activity_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:612 #, elixir-autogen, elixir-format msgid "organizer" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:756 +#: lib/who_need_help_web/live/request_live/show.ex:761 #, elixir-autogen, elixir-format msgid "participants only" msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:328 -#: lib/who_need_help_web/live/request_live/show.ex:887 +#: lib/who_need_help_web/live/request_live/show.ex:900 #, elixir-autogen, elixir-format msgid "people" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:901 +#: lib/who_need_help_web/live/request_live/show.ex:914 #, elixir-autogen, elixir-format msgid "proximity supported" msgstr "" #: lib/who_need_help_web/live/profile_live.ex:204 -#: lib/who_need_help_web/live/request_live/show.ex:891 +#: lib/who_need_help_web/live/request_live/show.ex:904 #, elixir-autogen, elixir-format msgid "rating" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:748 +#: lib/who_need_help_web/live/request_live/show.ex:753 #, elixir-autogen, elixir-format msgid "rating %{rating}" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:41 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "" @@ -2545,12 +2529,12 @@ msgstr "" msgid "unique people" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:414 -#: lib/who_need_help_web/live/activity_live/show.ex:565 -#: lib/who_need_help_web/live/activity_live/show.ex:624 +#: lib/who_need_help_web/live/activity_live/show.ex:418 +#: lib/who_need_help_web/live/activity_live/show.ex:572 +#: lib/who_need_help_web/live/activity_live/show.ex:631 #: lib/who_need_help_web/live/profile_live.ex:277 -#: lib/who_need_help_web/live/request_live/show.ex:735 -#: lib/who_need_help_web/live/request_live/show.ex:932 +#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:945 #, elixir-autogen, elixir-format msgid "unverified" msgstr "" @@ -2560,13 +2544,13 @@ msgstr "" msgid "urgent" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:413 -#: lib/who_need_help_web/live/activity_live/show.ex:564 -#: lib/who_need_help_web/live/activity_live/show.ex:623 +#: lib/who_need_help_web/live/activity_live/show.ex:417 +#: lib/who_need_help_web/live/activity_live/show.ex:571 +#: lib/who_need_help_web/live/activity_live/show.ex:630 #: lib/who_need_help_web/live/profile_live.ex:276 #: lib/who_need_help_web/live/request_live/index.ex:333 -#: lib/who_need_help_web/live/request_live/show.ex:734 -#: lib/who_need_help_web/live/request_live/show.ex:931 +#: lib/who_need_help_web/live/request_live/show.ex:739 +#: lib/who_need_help_web/live/request_live/show.ex:944 #, elixir-autogen, elixir-format msgid "verified" msgstr "" @@ -2576,18 +2560,18 @@ msgstr "" msgid "verified handovers" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:660 +#: lib/who_need_help_web/live/request_live/show.ex:665 #, elixir-autogen, elixir-format msgid "← All requests" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:113 -#: lib/who_need_help_web/live/activity_live/show.ex:348 +#: lib/who_need_help_web/live/activity_live/new.ex:118 +#: lib/who_need_help_web/live/activity_live/show.ex:352 #, elixir-autogen, elixir-format msgid "← Back to activities" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:152 +#: lib/who_need_help_web/live/request_live/new.ex:157 #, elixir-autogen, elixir-format msgid "← Back to requests" msgstr "" @@ -2648,15 +2632,15 @@ msgstr "" msgid "Unusual action rate" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:448 -#: lib/who_need_help_web/live/request_live/show.ex:768 +#: lib/who_need_help_web/live/activity_live/show.ex:452 +#: lib/who_need_help_web/live/request_live/show.ex:773 #, elixir-autogen, elixir-format msgid "Load earlier messages" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:273 #: lib/who_need_help_web/live/activity_live/index.ex:310 -#: lib/who_need_help_web/live/category_proposal_live.ex:162 +#: lib/who_need_help_web/live/category_proposal_live.ex:168 #: lib/who_need_help_web/live/leaderboard_live.ex:87 #: lib/who_need_help_web/live/moderation_live.ex:384 #: lib/who_need_help_web/live/moderation_live.ex:419 @@ -2671,9 +2655,9 @@ msgid "Load more" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:284 -#: lib/who_need_help_web/live/activity_live/show.ex:498 +#: lib/who_need_help_web/live/activity_live/show.ex:502 #: lib/who_need_help_web/live/request_live/index.ex:314 -#: lib/who_need_help_web/live/request_live/show.ex:849 +#: lib/who_need_help_web/live/request_live/show.ex:862 #, elixir-autogen, elixir-format msgid "The map is unavailable in this browser. Request details remain usable." msgstr "" @@ -2694,19 +2678,19 @@ msgstr "" msgid "Could not complete moderation. Please try again." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:336 -#: lib/who_need_help_web/live/category_proposal_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:621 +#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/category_proposal_live.ex:61 +#: lib/who_need_help_web/live/request_live/show.ex:626 #, elixir-autogen, elixir-format msgid "Could not complete the action. Please try again." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/activity_live/new.ex:110 #, elixir-autogen, elixir-format msgid "Could not publish the activity. Please try again." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:144 +#: lib/who_need_help_web/live/request_live/new.ex:149 #, elixir-autogen, elixir-format msgid "Could not publish the request. Please try again." msgstr "" @@ -2721,32 +2705,32 @@ msgstr "" msgid "JavaScript is required to confirm this protected email link." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:594 +#: lib/who_need_help_web/live/request_live/show.ex:599 #, elixir-autogen, elixir-format msgid "Last update: %{time}" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:617 +#: lib/who_need_help_web/live/request_live/show.ex:622 #, elixir-autogen, elixir-format msgid "Live location sharing is not active." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:592 +#: lib/who_need_help_web/live/request_live/show.ex:597 #, elixir-autogen, elixir-format msgid "Shared location" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:329 +#: lib/who_need_help_web/live/activity_live/show.ex:333 #, elixir-autogen, elixir-format msgid "The organizer cannot leave the activity." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:326 +#: lib/who_need_help_web/live/activity_live/show.ex:330 #, elixir-autogen, elixir-format msgid "The organizer is already attending." msgstr "" -#: lib/who_need_help_web/controllers/user_registration_controller.ex:60 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:58 #, elixir-autogen, elixir-format msgid "The registration form is invalid." msgstr "" @@ -2766,22 +2750,22 @@ msgstr "" msgid "The sign-in form is invalid." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:323 +#: lib/who_need_help_web/live/activity_live/show.ex:327 #, elixir-autogen, elixir-format msgid "This activity is no longer available." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:616 +#: lib/who_need_help_web/live/request_live/show.ex:621 #, elixir-autogen, elixir-format msgid "This match is no longer active." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:57 +#: lib/who_need_help_web/live/category_proposal_live.ex:58 #, elixir-autogen, elixir-format msgid "This proposal is no longer available." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:614 +#: lib/who_need_help_web/live/request_live/show.ex:619 #, elixir-autogen, elixir-format msgid "This request has expired." msgstr "" @@ -2796,12 +2780,12 @@ msgstr "" msgid "Your secure email link is ready. Continue to sign in." msgstr "" -#: lib/who_need_help_web/controllers/user_settings_controller.ex:128 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:130 #, elixir-autogen, elixir-format msgid "The confirmation email could not be sent. Please try again." msgstr "" -#: lib/who_need_help_web/controllers/user_registration_controller.ex:109 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:107 #, elixir-autogen, elixir-format msgid "If this address can be registered or signed in, login instructions will arrive shortly." msgstr "" @@ -2815,3 +2799,185 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Too many email-change requests in the configured time window." msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:32 +#, elixir-autogen, elixir-format +msgid "A Google account is connected. You can use it to sign in." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:207 +#, elixir-autogen, elixir-format +msgid "A different Google account is already connected." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:17 +#, elixir-autogen, elixir-format +msgid "A password is not required. We will email a one-time confirmation link. You can add a password later in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:170 +#, elixir-autogen, elixir-format +msgid "An account with this email already exists. Sign in by email or password, then connect Google in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:37 +#, elixir-autogen, elixir-format +msgid "Confirm that you are 18 or older and accept the safety rules first." +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:49 +#, elixir-autogen, elixir-format +msgid "Connect Google account" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Connect Google only after signing in here. We never merge accounts automatically by matching email addresses." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "Create account and email me a link" +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:99 +#, elixir-autogen, elixir-format +msgid "Email me a sign-in link" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:215 +#, elixir-autogen, elixir-format +msgid "Google account connection session is invalid." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:97 +#, elixir-autogen, elixir-format +msgid "Google did not provide a verified email address." +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:29 +#, elixir-autogen, elixir-format +msgid "Google sign-in" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:197 +#, elixir-autogen, elixir-format +msgid "Google sign-in connected." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:123 +#, elixir-autogen, elixir-format +msgid "Google sign-in could not be started." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:102 +#, elixir-autogen, elixir-format +msgid "Google sign-in failed. Please try again." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:128 +#, elixir-autogen, elixir-format +msgid "Google sign-in is not configured yet." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:87 +#, elixir-autogen, elixir-format +msgid "Google sign-in session expired. Please start again." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:91 +#, elixir-autogen, elixir-format +msgid "Google sign-in session is invalid." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:47 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Google sign-in will be available after the operator configures it." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 +#, elixir-autogen, elixir-format +msgid "Sending link..." +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:74 +#, elixir-autogen, elixir-format +msgid "Sign in with Google" +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Sign up with Google" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:202 +#, elixir-autogen, elixir-format +msgid "That Google account belongs to another local account." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:45 +#, elixir-autogen, elixir-format +msgid "The Google registration form is invalid." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:260 +#, elixir-autogen, elixir-format +msgid "This Google account cannot be used right now." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:144 +#, elixir-autogen, elixir-format +msgid "This Google account is not connected yet. Create an account or sign in by email and connect Google in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:109 +#, elixir-autogen, elixir-format +msgid "This works only if you previously added a password in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#, elixir-autogen, elixir-format +msgid "Use a password instead" +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "We will send a one-time sign-in link. No password is required." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:163 +#, elixir-autogen, elixir-format +msgid "Your account was created with Google." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:51 +#, elixir-autogen, elixir-format +msgid "or sign up with email" +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 +#, elixir-autogen, elixir-format +msgid "or use email" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Disconnect Google" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Disconnect Google sign-in from this account?" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:60 +#, elixir-autogen, elixir-format +msgid "Google sign-in disconnected." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:65 +#, elixir-autogen, elixir-format +msgid "No Google account is connected." +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index bc2379b..6aac897 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -17,7 +17,7 @@ msgstr "" msgid "Account settings" msgstr "" -#: lib/who_need_help_web/components/core_components.ex:393 +#: lib/who_need_help_web/components/core_components.ex:446 #, elixir-autogen, elixir-format msgid "Actions" msgstr "" @@ -25,7 +25,7 @@ msgstr "" #: lib/who_need_help_web/components/layouts.ex:70 #: lib/who_need_help_web/components/layouts.ex:119 #: lib/who_need_help_web/controllers/page_html/home.html.heex:21 -#: lib/who_need_help_web/live/request_live/new.ex:22 +#: lib/who_need_help_web/live/request_live/new.ex:23 #, elixir-autogen, elixir-format msgid "Ask for help" msgstr "" @@ -108,7 +108,7 @@ msgstr "" msgid "close" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:363 +#: lib/who_need_help_web/live/activity_live/show.ex:367 #, elixir-autogen, elixir-format msgid "%{approved}/%{capacity} approved" msgstr "" @@ -118,37 +118,37 @@ msgstr "" msgid "%{count} community votes" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:745 +#: lib/who_need_help_web/live/request_live/show.ex:750 #, elixir-autogen, elixir-format msgid "%{count} unique people" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:829 +#: lib/who_need_help_web/live/request_live/show.ex:842 #, elixir-autogen, elixir-format msgid "1 — unsafe" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:828 +#: lib/who_need_help_web/live/request_live/show.ex:841 #, elixir-autogen, elixir-format msgid "2 — poor" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:827 +#: lib/who_need_help_web/live/request_live/show.ex:840 #, elixir-autogen, elixir-format msgid "3 — okay" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:826 +#: lib/who_need_help_web/live/request_live/show.ex:839 #, elixir-autogen, elixir-format msgid "4 — good" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:825 +#: lib/who_need_help_web/live/request_live/show.ex:838 #, elixir-autogen, elixir-format msgid "5 — excellent" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_controller.ex:118 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:120 #, elixir-autogen, elixir-format msgid "A link to confirm your email change has been sent to the new address." msgstr "" @@ -158,12 +158,12 @@ msgstr "" msgid "A signal requests human review; optional tracking absence is not proof of abuse." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:859 +#: lib/who_need_help_web/live/request_live/show.ex:872 #, elixir-autogen, elixir-format msgid "Accept only if you can complete this safely and for free." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:633 +#: lib/who_need_help_web/live/request_live/show.ex:638 #, elixir-autogen, elixir-format msgid "Accepted" msgstr "" @@ -184,7 +184,7 @@ msgstr "" msgid "Accounts" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:855 +#: lib/who_need_help_web/live/request_live/show.ex:868 #, elixir-autogen, elixir-format msgid "Action center" msgstr "" @@ -206,7 +206,7 @@ msgstr "" msgid "Activities are optional plans, not urgent help, and do not affect helper reputation. Meet in a suitable public place first. Organizers approve every participant; exact coordinates and group chat are limited to approved participants. Share route and safety plans for hikes, and never publish home addresses, access codes, or travel documents." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:163 +#: lib/who_need_help_web/live/activity_live/new.ex:168 #, elixir-autogen, elixir-format msgid "Activity details" msgstr "" @@ -217,12 +217,12 @@ msgid "Activity hidden." msgstr "" #: lib/who_need_help_web/live/activity_live/show.ex:18 -#: lib/who_need_help_web/live/activity_live/show.ex:177 +#: lib/who_need_help_web/live/activity_live/show.ex:181 #, elixir-autogen, elixir-format msgid "Activity is unavailable." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:36 +#: lib/who_need_help_web/live/activity_live/new.ex:41 #, elixir-autogen, elixir-format msgid "Activity published. You approve every participant." msgstr "" @@ -233,7 +233,7 @@ msgid "Activity restored." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:210 -#: lib/who_need_help_web/live/activity_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Activity type" msgstr "" @@ -264,7 +264,7 @@ msgstr "" msgid "Already registered?" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:603 +#: lib/who_need_help_web/live/request_live/show.ex:608 #, elixir-autogen, elixir-format msgid "Another helper already accepted this request." msgstr "" @@ -274,60 +274,60 @@ msgstr "" msgid "Any urgency" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:524 +#: lib/who_need_help_web/live/activity_live/show.ex:528 #, elixir-autogen, elixir-format msgid "Approval pending" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:574 +#: lib/who_need_help_web/live/activity_live/show.ex:581 #, elixir-autogen, elixir-format msgid "Approve" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:435 +#: lib/who_need_help_web/live/activity_live/show.ex:439 #, elixir-autogen, elixir-format msgid "Approved group chat" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:600 +#: lib/who_need_help_web/live/activity_live/show.ex:607 #, elixir-autogen, elixir-format msgid "Approved participants" msgstr "" #: lib/who_need_help_web/controllers/page_html/home.html.heex:51 -#: lib/who_need_help_web/live/activity_live/new.ex:258 -#: lib/who_need_help_web/live/request_live/new.ex:306 +#: lib/who_need_help_web/live/activity_live/new.ex:263 +#: lib/who_need_help_web/live/request_live/new.ex:311 #, elixir-autogen, elixir-format msgid "Approximate area" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Approximate public meeting area" msgstr "" #: lib/who_need_help_web/live/profile_live.ex:167 -#: lib/who_need_help_web/live/request_live/show.ex:639 +#: lib/who_need_help_web/live/request_live/show.ex:644 #, elixir-autogen, elixir-format msgid "Approximate publicly" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:314 +#: lib/who_need_help_web/live/request_live/new.ex:319 #, elixir-autogen, elixir-format msgid "Approximate publicly (recommended)" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:702 +#: lib/who_need_help_web/live/request_live/show.ex:707 #, elixir-autogen, elixir-format msgid "Area:" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:154 +#: lib/who_need_help_web/live/request_live/new.ex:159 #, elixir-autogen, elixir-format msgid "Ask for urgent help" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:92 +#: lib/who_need_help_web/live/category_proposal_live.ex:98 #, elixir-autogen, elixir-format msgid "Bicycle puncture" msgstr "" @@ -342,12 +342,12 @@ msgstr "" msgid "Block and report" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:641 +#: lib/who_need_help_web/live/activity_live/show.ex:648 #, elixir-autogen, elixir-format msgid "Block organizer" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1062 +#: lib/who_need_help_web/live/request_live/show.ex:1075 #, elixir-autogen, elixir-format msgid "Block this user" msgstr "" @@ -362,7 +362,7 @@ msgstr "" msgid "Both confirm and the helper enters a one-time code." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:118 +#: lib/who_need_help_web/live/request_live/new.ex:123 #, elixir-autogen, elixir-format msgid "Briefly describe the help you need" msgstr "" @@ -382,33 +382,33 @@ msgstr "" msgid "COMMUNITY TRUST" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:80 +#: lib/who_need_help_web/live/category_proposal_live.ex:81 #, elixir-autogen, elixir-format msgid "COMMUNITY-DRIVEN TAXONOMY" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:594 +#: lib/who_need_help_web/live/activity_live/show.ex:601 #, elixir-autogen, elixir-format msgid "Cancel activity" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:873 -#: lib/who_need_help_web/live/request_live/show.ex:967 +#: lib/who_need_help_web/live/request_live/show.ex:886 +#: lib/who_need_help_web/live/request_live/show.ex:980 #, elixir-autogen, elixir-format msgid "Cancel request" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:168 -#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/activity_live/show.ex:344 #: lib/who_need_help_web/live/request_live/index.ex:184 #: lib/who_need_help_web/live/request_live/index.ex:185 -#: lib/who_need_help_web/live/request_live/show.ex:636 +#: lib/who_need_help_web/live/request_live/show.ex:641 #, elixir-autogen, elixir-format msgid "Cancelled" msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:225 -#: lib/who_need_help_web/live/request_live/new.ex:180 +#: lib/who_need_help_web/live/request_live/new.ex:185 #, elixir-autogen, elixir-format msgid "Category" msgstr "" @@ -418,13 +418,13 @@ msgstr "" msgid "Category created and proposal approved." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:211 -#: lib/who_need_help_web/live/request_live/show.ex:691 +#: lib/who_need_help_web/live/request_live/new.ex:216 +#: lib/who_need_help_web/live/request_live/show.ex:696 #, elixir-autogen, elixir-format msgid "Category details" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:66 +#: lib/who_need_help_web/live/category_proposal_live.ex:67 #: lib/who_need_help_web/live/moderation_live.ex:466 #, elixir-autogen, elixir-format msgid "Category proposals" @@ -441,7 +441,7 @@ msgid "Change role" msgstr "" #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:21 -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:47 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:107 #, elixir-autogen, elixir-format msgid "Changing..." msgstr "" @@ -451,31 +451,31 @@ msgstr "" msgid "Choose a category, urgency, and safe location visibility." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:140 +#: lib/who_need_help_web/live/activity_live/new.ex:145 #, elixir-autogen, elixir-format msgid "Choose activity" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:181 +#: lib/who_need_help_web/live/request_live/new.ex:186 #, elixir-autogen, elixir-format msgid "Choose category" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:175 -#: lib/who_need_help_web/live/activity_live/new.ex:202 -#: lib/who_need_help_web/live/request_live/new.ex:223 -#: lib/who_need_help_web/live/request_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:180 +#: lib/who_need_help_web/live/activity_live/new.ex:207 +#: lib/who_need_help_web/live/request_live/new.ex:228 +#: lib/who_need_help_web/live/request_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Choose…" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:251 +#: lib/who_need_help_web/live/activity_live/new.ex:256 #, elixir-autogen, elixir-format msgid "City centre, near the main entrance" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:654 -#: lib/who_need_help_web/live/request_live/show.ex:1078 +#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/request_live/show.ex:1096 #, elixir-autogen, elixir-format msgid "Clear" msgstr "" @@ -485,7 +485,7 @@ msgstr "" msgid "Close" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:220 +#: lib/who_need_help_web/live/activity_live/new.ex:225 #, elixir-autogen, elixir-format msgid "Coffee and conversation" msgstr "" @@ -495,7 +495,7 @@ msgstr "" msgid "Coffee, cinema, walks, and hikes are separate from urgent help and never affect helper ratings." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:835 +#: lib/who_need_help_web/live/request_live/show.ex:848 #, elixir-autogen, elixir-format msgid "Comment (optional)" msgstr "" @@ -506,19 +506,19 @@ msgid "Community helpers" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:262 -#: lib/who_need_help_web/live/activity_live/show.ex:392 -#: lib/who_need_help_web/live/activity_live/show.ex:546 -#: lib/who_need_help_web/live/activity_live/show.ex:603 +#: lib/who_need_help_web/live/activity_live/show.ex:396 +#: lib/who_need_help_web/live/activity_live/show.ex:553 +#: lib/who_need_help_web/live/activity_live/show.ex:610 #: lib/who_need_help_web/live/request_live/index.ex:287 #, elixir-autogen, elixir-format msgid "Community member" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:167 -#: lib/who_need_help_web/live/activity_live/show.ex:339 +#: lib/who_need_help_web/live/activity_live/show.ex:343 #: lib/who_need_help_web/live/request_live/index.ex:182 #: lib/who_need_help_web/live/request_live/index.ex:183 -#: lib/who_need_help_web/live/request_live/show.ex:635 +#: lib/who_need_help_web/live/request_live/show.ex:640 #, elixir-autogen, elixir-format msgid "Completed" msgstr "" @@ -533,24 +533,24 @@ msgstr "" msgid "Confirm and stay logged in" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:947 +#: lib/who_need_help_web/live/request_live/show.ex:960 #, elixir-autogen, elixir-format msgid "Confirm my part is complete" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:42 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:102 #, elixir-autogen, elixir-format msgid "Confirm new password" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:51 -#: lib/who_need_help_web/live/request_live/show.ex:611 +#: lib/who_need_help_web/live/category_proposal_live.ex:52 +#: lib/who_need_help_web/live/request_live/show.ex:616 #, elixir-autogen, elixir-format msgid "Confirm your account and ensure it is active first." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:100 -#: lib/who_need_help_web/live/request_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/request_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Confirm your email and ensure your account is active before publishing." msgstr "" @@ -573,17 +573,12 @@ msgid "Create a clear plan in a public place." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:188 -#: lib/who_need_help_web/live/activity_live/new.ex:11 +#: lib/who_need_help_web/live/activity_live/new.ex:12 #, elixir-autogen, elixir-format msgid "Create activity" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:59 -#, elixir-autogen, elixir-format -msgid "Create an account" -msgstr "" - -#: lib/who_need_help_web/live/activity_live/new.ex:115 +#: lib/who_need_help_web/live/activity_live/new.ex:120 #, elixir-autogen, elixir-format msgid "Create an activity" msgstr "" @@ -598,18 +593,13 @@ msgstr "" msgid "Create urgent request" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:58 -#, elixir-autogen, elixir-format -msgid "Creating account..." -msgstr "" - -#: lib/who_need_help_web/live/activity_live/show.ex:663 +#: lib/who_need_help_web/live/activity_live/show.ex:675 #, elixir-autogen, elixir-format msgid "Dangerous plan" msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:248 -#: lib/who_need_help_web/live/request_live/show.ex:1086 +#: lib/who_need_help_web/live/request_live/show.ex:1104 #, elixir-autogen, elixir-format msgid "Dangerous request" msgstr "" @@ -629,7 +619,7 @@ msgstr "" msgid "Decisions and access to reported chat evidence are written to the audit log." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:581 +#: lib/who_need_help_web/live/activity_live/show.ex:588 #, elixir-autogen, elixir-format msgid "Decline" msgstr "" @@ -639,7 +629,7 @@ msgstr "" msgid "Default location visibility" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:227 +#: lib/who_need_help_web/live/activity_live/new.ex:232 #, elixir-autogen, elixir-format msgid "Describe the public plan, approximate duration, and who might enjoy it." msgstr "" @@ -651,7 +641,7 @@ msgstr "" msgid "Dismissed" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:34 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -667,14 +657,14 @@ msgstr "" msgid "Don't have an account?" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:815 +#: lib/who_need_help_web/live/request_live/show.ex:820 #, elixir-autogen, elixir-format msgid "Double-blind review" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:25 -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 #: lib/who_need_help_web/controllers/user_session_html/new.html.heex:89 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:122 #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:15 #, elixir-autogen, elixir-format msgid "Email" @@ -711,7 +701,7 @@ msgstr "" msgid "English name" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:992 +#: lib/who_need_help_web/live/request_live/show.ex:1005 #, elixir-autogen, elixir-format msgid "Enter handover code" msgstr "" @@ -721,7 +711,7 @@ msgstr "" msgid "Exact address after matching" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:274 +#: lib/who_need_help_web/live/activity_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Exact coordinates are shown only to participants you approve." msgstr "" @@ -731,29 +721,29 @@ msgstr "" msgid "Exact for active match" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:316 -#: lib/who_need_help_web/live/request_live/show.ex:641 +#: lib/who_need_help_web/live/request_live/new.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:646 #, elixir-autogen, elixir-format msgid "Exact only for active match" msgstr "" #: lib/who_need_help_web/live/profile_live.ex:169 -#: lib/who_need_help_web/live/request_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:647 #, elixir-autogen, elixir-format msgid "Exact publicly" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:317 +#: lib/who_need_help_web/live/request_live/new.ex:322 #, elixir-autogen, elixir-format msgid "Exact publicly — explicit consent" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:710 +#: lib/who_need_help_web/live/request_live/show.ex:715 #, elixir-autogen, elixir-format msgid "Expires:" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:274 +#: lib/who_need_help_web/live/request_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Explain what is already arranged and what the volunteer needs to do." msgstr "" @@ -763,9 +753,9 @@ msgstr "" msgid "Find people to join" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:664 +#: lib/who_need_help_web/live/activity_live/show.ex:676 #: lib/who_need_help_web/live/moderation_live.ex:250 -#: lib/who_need_help_web/live/request_live/show.ex:1088 +#: lib/who_need_help_web/live/request_live/show.ex:1106 #, elixir-autogen, elixir-format msgid "Fraud" msgstr "" @@ -785,7 +775,7 @@ msgstr "" msgid "GitHub verification is unavailable until the operator configures its OAuth credentials. No manually entered link is treated as verified." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:975 +#: lib/who_need_help_web/live/request_live/show.ex:988 #, elixir-autogen, elixir-format msgid "HANDOVER CODE FOR THE HELPER" msgstr "" @@ -800,9 +790,9 @@ msgstr "" msgid "Handover code verified." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:662 +#: lib/who_need_help_web/live/activity_live/show.ex:674 #: lib/who_need_help_web/live/moderation_live.ex:249 -#: lib/who_need_help_web/live/request_live/show.ex:1087 +#: lib/who_need_help_web/live/request_live/show.ex:1105 #, elixir-autogen, elixir-format msgid "Harassment" msgstr "" @@ -822,7 +812,7 @@ msgstr "" msgid "Helper" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:878 +#: lib/who_need_help_web/live/request_live/show.ex:891 #, elixir-autogen, elixir-format msgid "Helper: %{name}" msgstr "" @@ -852,10 +842,10 @@ msgstr "" msgid "Hi %{email},\n\nYou can log into your account by visiting the URL below:\n\n%{url}\n\nIf you didn't request this email, please ignore this." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:259 +#: lib/who_need_help_web/live/activity_live/new.ex:264 #: lib/who_need_help_web/live/profile_live.ex:166 -#: lib/who_need_help_web/live/request_live/new.ex:315 -#: lib/who_need_help_web/live/request_live/show.ex:640 +#: lib/who_need_help_web/live/request_live/new.ex:320 +#: lib/who_need_help_web/live/request_live/show.ex:645 #, elixir-autogen, elixir-format msgid "Hidden" msgstr "" @@ -881,33 +871,34 @@ msgstr "" msgid "Human decisions retained" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:48 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:39 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:85 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "" #: lib/who_need_help_web/controllers/page_html/home.html.heex:56 -#: lib/who_need_help_web/live/request_live/show.ex:862 +#: lib/who_need_help_web/live/request_live/show.ex:875 #, elixir-autogen, elixir-format msgid "I can help" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:127 +#: lib/who_need_help_web/live/request_live/new.ex:132 #, elixir-autogen, elixir-format msgid "I confirm there is no immediate danger, people and vehicles are away from active traffic or emergency services have already been contacted, and the requested task is lawful." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:121 +#: lib/who_need_help_web/live/request_live/new.ex:126 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, medical advice request, controlled substance, or request to buy medicine on my behalf." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:133 +#: lib/who_need_help_web/live/request_live/new.ex:138 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, there is no immediate danger, and the requested task is lawful." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:298 +#: lib/who_need_help_web/live/activity_live/new.ex:303 #, elixir-autogen, elixir-format msgid "I will approve participants individually, keep sensitive details in the approved group, and use an appropriate public first meeting point." msgstr "" @@ -922,21 +913,21 @@ msgstr "" msgid "If your email is in our system, you will receive instructions for logging in shortly." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/activity_live/show.ex:678 #: lib/who_need_help_web/live/moderation_live.ex:253 -#: lib/who_need_help_web/live/request_live/show.ex:1091 +#: lib/who_need_help_web/live/request_live/show.ex:1109 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1027 +#: lib/who_need_help_web/live/request_live/show.ex:1040 #, elixir-autogen, elixir-format msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops." msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:180 #: lib/who_need_help_web/live/request_live/index.ex:181 -#: lib/who_need_help_web/live/request_live/show.ex:634 +#: lib/who_need_help_web/live/request_live/show.ex:639 #, elixir-autogen, elixir-format msgid "In progress" msgstr "" @@ -956,49 +947,49 @@ msgstr "" msgid "Invalid email or password" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:314 +#: lib/who_need_help_web/live/activity_live/show.ex:318 #, elixir-autogen, elixir-format msgid "Join requests are closed." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:380 +#: lib/who_need_help_web/live/activity_live/show.ex:384 #, elixir-autogen, elixir-format msgid "Join requests close" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:239 +#: lib/who_need_help_web/live/activity_live/new.ex:244 #, elixir-autogen, elixir-format msgid "Join requests close (UTC)" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:282 -#: lib/who_need_help_web/live/request_live/new.ex:340 +#: lib/who_need_help_web/live/activity_live/new.ex:287 +#: lib/who_need_help_web/live/request_live/new.ex:345 #, elixir-autogen, elixir-format msgid "Latitude" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:534 +#: lib/who_need_help_web/live/activity_live/show.ex:541 #, elixir-autogen, elixir-format msgid "Leave activity" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1025 +#: lib/who_need_help_web/live/request_live/show.ex:1038 #, elixir-autogen, elixir-format msgid "Live location" msgstr "" #: lib/who_need_help_web/controllers/page_html/safety.html.heex:51 -#: lib/who_need_help_web/live/activity_live/show.ex:503 +#: lib/who_need_help_web/live/activity_live/show.ex:507 #, elixir-autogen, elixir-format msgid "Location privacy" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:914 +#: lib/who_need_help_web/live/request_live/show.ex:927 #, elixir-autogen, elixir-format msgid "Location sharing is optional; missing evidence is not proof of abuse." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:312 +#: lib/who_need_help_web/live/request_live/new.ex:317 #, elixir-autogen, elixir-format msgid "Location visibility" msgstr "" @@ -1008,12 +999,12 @@ msgstr "" msgid "Location-supported people" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:717 +#: lib/who_need_help_web/live/request_live/show.ex:722 #, elixir-autogen, elixir-format msgid "Location:" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:102 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:136 #, elixir-autogen, elixir-format msgid "Log in and stay logged in" msgstr "" @@ -1023,23 +1014,18 @@ msgstr "" msgid "Log in instructions" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:139 #, elixir-autogen, elixir-format msgid "Log in only this time" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 -#, elixir-autogen, elixir-format -msgid "Log in with email" -msgstr "" - #: lib/who_need_help_web/controllers/user_session_controller.ex:117 #, elixir-autogen, elixir-format msgid "Logged out successfully." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:289 -#: lib/who_need_help_web/live/request_live/new.ex:347 +#: lib/who_need_help_web/live/activity_live/new.ex:294 +#: lib/who_need_help_web/live/request_live/new.ex:352 #, elixir-autogen, elixir-format msgid "Longitude" msgstr "" @@ -1059,7 +1045,7 @@ msgstr "" msgid "Manually added links are public and marked unverified. A verified badge is only granted after completing a provider authorization flow." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:591 +#: lib/who_need_help_web/live/activity_live/show.ex:598 #, elixir-autogen, elixir-format msgid "Mark completed" msgstr "" @@ -1071,12 +1057,12 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:178 #: lib/who_need_help_web/live/request_live/index.ex:179 -#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:637 #, elixir-autogen, elixir-format msgid "Matched" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:113 +#: lib/who_need_help_web/live/request_live/new.ex:118 #, elixir-autogen, elixir-format msgid "Medicine is ready at the pharmacy" msgstr "" @@ -1091,7 +1077,7 @@ msgstr "" msgid "Medicine pickup" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:156 +#: lib/who_need_help_web/live/request_live/new.ex:161 #, elixir-autogen, elixir-format msgid "Medicine pickup remains the first priority. Roadside categories use the same moderated, extensible request flow with details specific to each type of help." msgstr "" @@ -1106,7 +1092,7 @@ msgstr "" msgid "Meet in public first, keep exact details inside the approved group, and leave or report anything that feels unsafe." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:386 +#: lib/who_need_help_web/live/activity_live/show.ex:390 #, elixir-autogen, elixir-format msgid "Meeting area" msgstr "" @@ -1126,12 +1112,12 @@ msgstr "" msgid "Merge note" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:483 +#: lib/who_need_help_web/live/activity_live/show.ex:487 #, elixir-autogen, elixir-format msgid "Message the approved group" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:97 +#: lib/who_need_help_web/live/category_proposal_live.ex:103 #, elixir-autogen, elixir-format msgid "Mode" msgstr "" @@ -1165,7 +1151,7 @@ msgstr "" msgid "Nearby help" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:116 +#: lib/who_need_help_web/live/request_live/new.ex:121 #, elixir-autogen, elixir-format msgid "Need roadside help in a safe location" msgstr "" @@ -1175,15 +1161,15 @@ msgstr "" msgid "Network" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:94 #, elixir-autogen, elixir-format msgid "New password" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:207 -#: lib/who_need_help_web/live/activity_live/show.ex:296 -#: lib/who_need_help_web/live/request_live/new.ex:255 -#: lib/who_need_help_web/live/request_live/show.ex:563 +#: lib/who_need_help_web/live/activity_live/new.ex:212 +#: lib/who_need_help_web/live/activity_live/show.ex:300 +#: lib/who_need_help_web/live/request_live/new.ex:260 +#: lib/who_need_help_web/live/request_live/show.ex:568 #, elixir-autogen, elixir-format msgid "No" msgstr "" @@ -1193,8 +1179,8 @@ msgstr "" msgid "No completed help yet." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:451 -#: lib/who_need_help_web/live/request_live/show.ex:771 +#: lib/who_need_help_web/live/activity_live/show.ex:455 +#: lib/who_need_help_web/live/request_live/show.ex:776 #, elixir-autogen, elixir-format msgid "No messages yet." msgstr "" @@ -1214,12 +1200,12 @@ msgstr "" msgid "No open signals." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:586 +#: lib/who_need_help_web/live/activity_live/show.ex:593 #, elixir-autogen, elixir-format msgid "No pending join requests." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:134 +#: lib/who_need_help_web/live/category_proposal_live.ex:140 #, elixir-autogen, elixir-format msgid "No proposals yet." msgstr "" @@ -1252,9 +1238,9 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:168 #: lib/who_need_help_web/live/request_live/index.ex:169 #: lib/who_need_help_web/live/request_live/index.ex:243 -#: lib/who_need_help_web/live/request_live/new.ex:293 -#: lib/who_need_help_web/live/request_live/show.ex:623 -#: lib/who_need_help_web/live/request_live/show.ex:624 +#: lib/who_need_help_web/live/request_live/new.ex:298 +#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/show.ex:629 #, elixir-autogen, elixir-format msgid "Now" msgstr "" @@ -1279,17 +1265,17 @@ msgstr "" msgid "Only the conversation linked to this report is displayed." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:437 +#: lib/who_need_help_web/live/activity_live/show.ex:441 #, elixir-autogen, elixir-format msgid "Only the organizer and approved participants can read or send these messages." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:166 -#: lib/who_need_help_web/live/activity_live/show.ex:338 +#: lib/who_need_help_web/live/activity_live/show.ex:342 #: lib/who_need_help_web/live/moderation_live.ex:257 #: lib/who_need_help_web/live/request_live/index.ex:176 #: lib/who_need_help_web/live/request_live/index.ex:177 -#: lib/who_need_help_web/live/request_live/show.ex:631 +#: lib/who_need_help_web/live/request_live/show.ex:636 #, elixir-autogen, elixir-format msgid "Open" msgstr "" @@ -1299,7 +1285,7 @@ msgstr "" msgid "Open abuse signals" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:128 +#: lib/who_need_help_web/live/category_proposal_live.ex:134 #, elixir-autogen, elixir-format msgid "Open proposals" msgstr "" @@ -1309,50 +1295,50 @@ msgstr "" msgid "Optional external thank-you link" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1011 +#: lib/who_need_help_web/live/request_live/show.ex:1024 #, elixir-autogen, elixir-format msgid "Optional thank-you link" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:390 +#: lib/who_need_help_web/live/activity_live/show.ex:394 #, elixir-autogen, elixir-format msgid "Organizer" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:155 +#: lib/who_need_help_web/live/activity_live/show.ex:159 #, elixir-autogen, elixir-format msgid "Organizer blocked. Their activities and messages are hidden." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:539 +#: lib/who_need_help_web/live/activity_live/show.ex:546 #, elixir-autogen, elixir-format msgid "Organizer controls" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:399 +#: lib/who_need_help_web/live/activity_live/show.ex:403 #, elixir-autogen, elixir-format msgid "Organizer social links" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:667 +#: lib/who_need_help_web/live/activity_live/show.ex:679 #: lib/who_need_help_web/live/moderation_live.ex:254 #: lib/who_need_help_web/live/profile_live.ex:311 -#: lib/who_need_help_web/live/request_live/show.ex:1092 +#: lib/who_need_help_web/live/request_live/show.ex:1110 #, elixir-autogen, elixir-format msgid "Other" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:112 #, elixir-autogen, elixir-format msgid "Parent (optional)" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:459 +#: lib/who_need_help_web/live/activity_live/show.ex:463 #, elixir-autogen, elixir-format msgid "Participant" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:97 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:130 #, elixir-autogen, elixir-format msgid "Password" msgstr "" @@ -1362,27 +1348,27 @@ msgstr "" msgid "Password updated successfully." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:280 +#: lib/who_need_help_web/live/request_live/new.ex:285 #, elixir-autogen, elixir-format msgid "Pickup instructions (optional)" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:686 +#: lib/who_need_help_web/live/request_live/show.ex:691 #, elixir-autogen, elixir-format msgid "Pickup notes" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:225 +#: lib/who_need_help_web/live/activity_live/new.ex:230 #, elixir-autogen, elixir-format msgid "Plan and expectations" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:421 +#: lib/who_need_help_web/live/activity_live/show.ex:425 #, elixir-autogen, elixir-format msgid "Plan details" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:619 +#: lib/who_need_help_web/live/request_live/show.ex:624 #, elixir-autogen, elixir-format msgid "Please check the entered data." msgstr "" @@ -1392,12 +1378,12 @@ msgstr "" msgid "Please check the submitted fields." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:334 +#: lib/who_need_help_web/live/activity_live/show.ex:338 #, elixir-autogen, elixir-format msgid "Please check the submitted message." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:307 +#: lib/who_need_help_web/live/request_live/new.ex:312 #, elixir-autogen, elixir-format msgid "Podil, near the central pharmacy" msgstr "" @@ -1413,7 +1399,7 @@ msgstr "" msgid "Primary navigation" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:755 +#: lib/who_need_help_web/live/request_live/show.ex:760 #, elixir-autogen, elixir-format msgid "Private match chat" msgstr "" @@ -1429,7 +1415,7 @@ msgid "Profile updated." msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:251 -#: lib/who_need_help_web/live/request_live/show.ex:1089 +#: lib/who_need_help_web/live/request_live/show.ex:1107 #, elixir-autogen, elixir-format msgid "Prohibited item" msgstr "" @@ -1449,12 +1435,12 @@ msgstr "" msgid "Proposal rejected." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:86 +#: lib/who_need_help_web/live/category_proposal_live.ex:87 #, elixir-autogen, elixir-format msgid "Proposals and votes guide moderators. Approval remains a human decision." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:91 +#: lib/who_need_help_web/live/category_proposal_live.ex:97 #, elixir-autogen, elixir-format msgid "Proposed category" msgstr "" @@ -1464,7 +1450,7 @@ msgstr "" msgid "Public location can be hidden, approximate, or explicitly exact. Live tracking is optional. A browser shares only while its page is open; Android shows a persistent foreground-service notification while sharing. The current exact point is deleted when sharing stops, a match ends, or either participant blocks the other; derived movement and proximity evidence may remain." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:256 +#: lib/who_need_help_web/live/activity_live/new.ex:261 #, elixir-autogen, elixir-format msgid "Public map visibility" msgstr "" @@ -1479,28 +1465,28 @@ msgstr "" msgid "Public reputation" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:117 +#: lib/who_need_help_web/live/activity_live/new.ex:122 #, elixir-autogen, elixir-format msgid "Publish a clear public plan. Join requests stay pending until you approve them." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:310 +#: lib/who_need_help_web/live/activity_live/new.ex:315 #, elixir-autogen, elixir-format msgid "Publish activity" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:123 +#: lib/who_need_help_web/live/category_proposal_live.ex:129 #, elixir-autogen, elixir-format msgid "Publish proposal" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:364 +#: lib/who_need_help_web/live/request_live/new.ex:369 #, elixir-autogen, elixir-format msgid "Publish request" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:308 -#: lib/who_need_help_web/live/request_live/new.ex:362 +#: lib/who_need_help_web/live/activity_live/new.ex:313 +#: lib/who_need_help_web/live/request_live/new.ex:367 #, elixir-autogen, elixir-format msgid "Publishing…" msgstr "" @@ -1526,17 +1512,17 @@ msgid "Ranking prioritizes unique people helped with optional location-supported msgstr "" #: lib/who_need_help_web/live/leaderboard_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:823 +#: lib/who_need_help_web/live/request_live/show.ex:836 #, elixir-autogen, elixir-format msgid "Rating" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:40 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 #, elixir-autogen, elixir-format msgid "Read the" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:660 +#: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" msgstr "" @@ -1561,22 +1547,22 @@ msgstr "" msgid "Remove" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:468 +#: lib/who_need_help_web/live/activity_live/show.ex:472 #, elixir-autogen, elixir-format msgid "Report" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:789 +#: lib/who_need_help_web/live/request_live/show.ex:794 #, elixir-autogen, elixir-format msgid "Report message" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1084 +#: lib/who_need_help_web/live/request_live/show.ex:1102 #, elixir-autogen, elixir-format msgid "Report reason" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:123 +#: lib/who_need_help_web/live/activity_live/show.ex:126 #: lib/who_need_help_web/live/request_live/show.ex:261 #, elixir-autogen, elixir-format msgid "Report sent to moderators." @@ -1597,7 +1583,7 @@ msgstr "" msgid "Reports" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1053 +#: lib/who_need_help_web/live/request_live/show.ex:1066 #, elixir-autogen, elixir-format msgid "Reports go to moderators. Blocking hides requests in both directions and prevents new chat messages." msgstr "" @@ -1607,7 +1593,7 @@ msgstr "" msgid "Request cancelled." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:301 +#: lib/who_need_help_web/live/request_live/new.ex:306 #, elixir-autogen, elixir-format msgid "Request expires (UTC)" msgstr "" @@ -1622,22 +1608,22 @@ msgstr "" msgid "Request restored." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:521 +#: lib/who_need_help_web/live/activity_live/show.ex:525 #, elixir-autogen, elixir-format msgid "Request to join" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:725 +#: lib/who_need_help_web/live/request_live/show.ex:730 #, elixir-autogen, elixir-format msgid "Requester links:" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:706 +#: lib/who_need_help_web/live/request_live/show.ex:711 #, elixir-autogen, elixir-format msgid "Requester:" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:745 #, elixir-autogen, elixir-format msgid "Requester: %{count} completed" msgstr "" @@ -1665,6 +1651,7 @@ msgid "Review note" msgstr "" #: lib/who_need_help_web/live/request_live/show.ex:233 +#: lib/who_need_help_web/live/request_live/show.ex:825 #, elixir-autogen, elixir-format msgid "Review saved. It appears after both participants review." msgstr "" @@ -1696,8 +1683,8 @@ msgstr "" msgid "Russian name (optional)" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:635 -#: lib/who_need_help_web/live/request_live/show.ex:1051 +#: lib/who_need_help_web/live/activity_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:1064 #, elixir-autogen, elixir-format msgid "Safety controls" msgstr "" @@ -1720,7 +1707,7 @@ msgstr "" msgid "Save" msgstr "" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:48 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:108 #, elixir-autogen, elixir-format msgid "Save Password" msgstr "" @@ -1733,21 +1720,21 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:172 #: lib/who_need_help_web/live/request_live/index.ex:173 #: lib/who_need_help_web/live/request_live/index.ex:245 -#: lib/who_need_help_web/live/request_live/new.ex:295 -#: lib/who_need_help_web/live/request_live/show.ex:627 -#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/new.ex:300 +#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:633 #, elixir-autogen, elixir-format msgid "Scheduled" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:486 -#: lib/who_need_help_web/live/request_live/show.ex:806 +#: lib/who_need_help_web/live/activity_live/show.ex:490 +#: lib/who_need_help_web/live/request_live/show.ex:811 #, elixir-autogen, elixir-format msgid "Send" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:676 -#: lib/who_need_help_web/live/request_live/show.ex:1101 +#: lib/who_need_help_web/live/activity_live/show.ex:688 +#: lib/who_need_help_web/live/request_live/show.ex:1119 #, elixir-autogen, elixir-format msgid "Send report" msgstr "" @@ -1757,12 +1744,12 @@ msgstr "" msgid "Set CODEX_SESSION_ID in the deployment environment before the submission recording." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1038 +#: lib/who_need_help_web/live/request_live/show.ex:1051 #, elixir-autogen, elixir-format msgid "Share location" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:282 +#: lib/who_need_help_web/live/request_live/new.ex:287 #, elixir-autogen, elixir-format msgid "Share only non-sensitive instructions. Exact details can wait for the matched chat." msgstr "" @@ -1772,8 +1759,8 @@ msgstr "" msgid "Short bio" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:219 -#: lib/who_need_help_web/live/request_live/new.ex:266 +#: lib/who_need_help_web/live/activity_live/new.ex:224 +#: lib/who_need_help_web/live/request_live/new.ex:271 #, elixir-autogen, elixir-format msgid "Short title" msgstr "" @@ -1803,8 +1790,8 @@ msgstr "" msgid "Social activities" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:100 -#: lib/who_need_help_web/live/category_proposal_live.ex:143 +#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:149 #, elixir-autogen, elixir-format msgid "Social activity" msgstr "" @@ -1844,24 +1831,24 @@ msgstr "" msgid "Social verification session is invalid." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:665 +#: lib/who_need_help_web/live/activity_live/show.ex:677 #: lib/who_need_help_web/live/moderation_live.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:1090 +#: lib/who_need_help_web/live/request_live/show.ex:1108 #, elixir-autogen, elixir-format msgid "Spam" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:940 +#: lib/who_need_help_web/live/request_live/show.ex:953 #, elixir-autogen, elixir-format msgid "Start helping" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:374 +#: lib/who_need_help_web/live/activity_live/show.ex:378 #, elixir-autogen, elixir-format msgid "Starts" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:234 +#: lib/who_need_help_web/live/activity_live/new.ex:239 #, elixir-autogen, elixir-format msgid "Starts at (UTC)" msgstr "" @@ -1871,7 +1858,7 @@ msgstr "" msgid "Status" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1046 +#: lib/who_need_help_web/live/request_live/show.ex:1059 #, elixir-autogen, elixir-format msgid "Stop and delete position" msgstr "" @@ -1886,7 +1873,7 @@ msgstr "" msgid "Structured fields must be a valid JSON object." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:837 +#: lib/who_need_help_web/live/request_live/show.ex:850 #, elixir-autogen, elixir-format msgid "Submit review" msgstr "" @@ -1897,13 +1884,13 @@ msgstr "" msgid "Suspended" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:604 +#: lib/who_need_help_web/live/request_live/show.ex:609 #, elixir-autogen, elixir-format msgid "That handover code is not correct." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:140 -#: lib/who_need_help_web/live/request_live/show.ex:278 +#: lib/who_need_help_web/live/activity_live/show.ex:144 +#: lib/who_need_help_web/live/request_live/show.ex:279 #, elixir-autogen, elixir-format msgid "That message cannot be reported from this view." msgstr "" @@ -1913,7 +1900,7 @@ msgstr "" msgid "That social account is already linked." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:608 +#: lib/who_need_help_web/live/request_live/show.ex:613 #, elixir-autogen, elixir-format msgid "That step is not available in the current state." msgstr "" @@ -1928,7 +1915,7 @@ msgstr "" msgid "That verification provider is not supported." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:312 +#: lib/who_need_help_web/live/activity_live/show.ex:316 #, elixir-autogen, elixir-format msgid "The approved group has reached its capacity." msgstr "" @@ -1948,7 +1935,7 @@ msgstr "" msgid "The link is invalid or it has expired." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:526 +#: lib/who_need_help_web/live/activity_live/show.ex:530 #, elixir-autogen, elixir-format msgid "The organizer has not approved your request yet." msgstr "" @@ -1963,48 +1950,48 @@ msgstr "" msgid "The platform never handles, requires, splits, or guarantees a payment." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:508 +#: lib/who_need_help_web/live/activity_live/show.ex:512 #, elixir-autogen, elixir-format msgid "The public map is approximate or hidden. Exact coordinates appear only after approval." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:648 +#: lib/who_need_help_web/live/activity_live/show.ex:660 #, elixir-autogen, elixir-format msgid "The report is scoped to the selected group message." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1076 +#: lib/who_need_help_web/live/request_live/show.ex:1094 #, elixir-autogen, elixir-format msgid "The report will be scoped to the selected chat message." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:315 +#: lib/who_need_help_web/live/activity_live/show.ex:319 #, elixir-autogen, elixir-format msgid "This action is unavailable." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:322 +#: lib/who_need_help_web/live/activity_live/show.ex:326 #, elixir-autogen, elixir-format msgid "This activity is no longer open." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:320 +#: lib/who_need_help_web/live/activity_live/show.ex:324 #, elixir-autogen, elixir-format msgid "This activity state has already changed." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:613 +#: lib/who_need_help_web/live/request_live/show.ex:618 #, elixir-autogen, elixir-format msgid "This interaction is blocked." msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:210 -#: lib/who_need_help_web/live/request_live/show.ex:665 +#: lib/who_need_help_web/live/request_live/show.ex:670 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:164 +#: lib/who_need_help_web/live/request_live/new.ex:169 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger. Never include prescription details, payment card data, access codes, or other sensitive information." msgstr "" @@ -2014,7 +2001,7 @@ msgstr "" msgid "This proposal has already been reviewed." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:56 +#: lib/who_need_help_web/live/category_proposal_live.ex:57 #, elixir-autogen, elixir-format msgid "This proposal is already closed." msgstr "" @@ -2024,13 +2011,13 @@ msgstr "" msgid "This report has no linked conversation." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1016 +#: lib/who_need_help_web/live/request_live/show.ex:1029 #, elixir-autogen, elixir-format msgid "This request already has a helper." msgstr "" #: lib/who_need_help_web/live/request_live/show.ex:74 -#: lib/who_need_help_web/live/request_live/show.ex:615 +#: lib/who_need_help_web/live/request_live/show.ex:620 #, elixir-autogen, elixir-format msgid "This request is no longer available." msgstr "" @@ -2048,28 +2035,29 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:170 #: lib/who_need_help_web/live/request_live/index.ex:171 #: lib/who_need_help_web/live/request_live/index.ex:244 -#: lib/who_need_help_web/live/request_live/new.ex:294 -#: lib/who_need_help_web/live/request_live/show.ex:625 -#: lib/who_need_help_web/live/request_live/show.ex:626 +#: lib/who_need_help_web/live/request_live/new.ex:299 +#: lib/who_need_help_web/live/request_live/show.ex:630 +#: lib/who_need_help_web/live/request_live/show.ex:631 #, elixir-autogen, elixir-format msgid "Today" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:103 -#: lib/who_need_help_web/live/activity_live/show.ex:332 -#: lib/who_need_help_web/live/category_proposal_live.ex:54 +#: lib/who_need_help_web/live/activity_live/new.ex:108 +#: lib/who_need_help_web/live/activity_live/show.ex:336 +#: lib/who_need_help_web/live/category_proposal_live.ex:55 #: lib/who_need_help_web/live/profile_live.ex:59 -#: lib/who_need_help_web/live/request_live/show.ex:618 +#: lib/who_need_help_web/live/request_live/show.ex:623 #, elixir-autogen, elixir-format msgid "Too many actions in the configured time window." msgstr "" +#: lib/who_need_help_web/controllers/google_auth_controller.ex:180 #: lib/who_need_help_web/controllers/user_registration_controller.ex:49 #, elixir-autogen, elixir-format msgid "Too many registration attempts in the configured time window." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:142 +#: lib/who_need_help_web/live/request_live/new.ex:147 #, elixir-autogen, elixir-format msgid "Too many requests in the configured time window." msgstr "" @@ -2079,7 +2067,7 @@ msgstr "" msgid "Too many sign-in emails in the configured time window." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:107 +#: lib/who_need_help_web/live/category_proposal_live.ex:113 #, elixir-autogen, elixir-format msgid "Top level" msgstr "" @@ -2089,7 +2077,7 @@ msgstr "" msgid "Total" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:246 +#: lib/who_need_help_web/live/activity_live/new.ex:251 #, elixir-autogen, elixir-format msgid "Total group capacity" msgstr "" @@ -2109,7 +2097,7 @@ msgstr "" msgid "Unblock" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:1069 +#: lib/who_need_help_web/live/request_live/show.ex:1082 #, elixir-autogen, elixir-format msgid "Unblock this user" msgstr "" @@ -2125,18 +2113,18 @@ msgid "Update email instructions" msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:240 -#: lib/who_need_help_web/live/request_live/new.ex:291 +#: lib/who_need_help_web/live/request_live/new.ex:296 #, elixir-autogen, elixir-format msgid "Urgency" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:99 -#: lib/who_need_help_web/live/category_proposal_live.ex:142 +#: lib/who_need_help_web/live/category_proposal_live.ex:105 +#: lib/who_need_help_web/live/category_proposal_live.ex:148 #, elixir-autogen, elixir-format msgid "Urgent help" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:123 +#: lib/who_need_help_web/live/activity_live/new.ex:128 #, elixir-autogen, elixir-format msgid "Use a public first meeting point. Do not publish a home address, access code, travel document, or other sensitive information." msgstr "" @@ -2151,8 +2139,8 @@ msgstr "" msgid "Use light theme" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:271 -#: lib/who_need_help_web/live/request_live/new.ex:329 +#: lib/who_need_help_web/live/activity_live/new.ex:276 +#: lib/who_need_help_web/live/request_live/new.ex:334 #, elixir-autogen, elixir-format msgid "Use my current foreground location" msgstr "" @@ -2174,7 +2162,7 @@ msgstr "" msgid "User" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:307 +#: lib/who_need_help_web/live/request_live/show.ex:308 #, elixir-autogen, elixir-format msgid "User blocked. Their requests and new messages are hidden." msgstr "" @@ -2195,7 +2183,7 @@ msgid "User status updated." msgstr "" #: lib/who_need_help_web/live/profile_live.ex:87 -#: lib/who_need_help_web/live/request_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:322 #, elixir-autogen, elixir-format msgid "User unblocked." msgstr "" @@ -2210,7 +2198,7 @@ msgstr "" msgid "Verify GitHub" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:996 +#: lib/who_need_help_web/live/request_live/show.ex:1009 #, elixir-autogen, elixir-format msgid "Verify handover" msgstr "" @@ -2230,11 +2218,12 @@ msgstr "" msgid "Visible reviews" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:866 +#: lib/who_need_help_web/live/request_live/show.ex:879 #, elixir-autogen, elixir-format msgid "Waiting for a nearby volunteer." msgstr "" +#: lib/who_need_help_web/controllers/google_auth_controller.ex:137 #: lib/who_need_help_web/controllers/user_session_controller.ex:26 #: lib/who_need_help_web/controllers/user_session_controller.ex:51 #, elixir-autogen, elixir-format @@ -2246,18 +2235,18 @@ msgstr "" msgid "What Codex contributed" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:673 -#: lib/who_need_help_web/live/request_live/show.ex:1098 +#: lib/who_need_help_web/live/activity_live/show.ex:685 +#: lib/who_need_help_web/live/request_live/show.ex:1116 #, elixir-autogen, elixir-format msgid "What happened?" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:83 +#: lib/who_need_help_web/live/category_proposal_live.ex:84 #, elixir-autogen, elixir-format msgid "What help category is missing?" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:272 +#: lib/who_need_help_web/live/request_live/new.ex:277 #, elixir-autogen, elixir-format msgid "What help do you need?" msgstr "" @@ -2277,7 +2266,7 @@ msgstr "" msgid "Who Need Help home" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:53 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "Who Need Help is not an emergency or medical service. Medicine requests are limited to pickup of legal items already purchased or reserved." msgstr "" @@ -2292,40 +2281,40 @@ msgstr "" msgid "Who needs help?" msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:121 +#: lib/who_need_help_web/live/category_proposal_live.ex:127 #, elixir-autogen, elixir-format msgid "Why is this useful?" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:957 +#: lib/who_need_help_web/live/request_live/show.ex:970 #, elixir-autogen, elixir-format msgid "Withdraw from this request" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:802 +#: lib/who_need_help_web/live/request_live/show.ex:807 #, elixir-autogen, elixir-format msgid "Write a safe coordination message…" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:204 -#: lib/who_need_help_web/live/activity_live/show.ex:294 -#: lib/who_need_help_web/live/request_live/new.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:561 +#: lib/who_need_help_web/live/activity_live/new.ex:209 +#: lib/who_need_help_web/live/activity_live/show.ex:298 +#: lib/who_need_help_web/live/request_live/new.ex:257 +#: lib/who_need_help_web/live/request_live/show.ex:566 #, elixir-autogen, elixir-format msgid "Yes" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:309 +#: lib/who_need_help_web/live/activity_live/show.ex:313 #, elixir-autogen, elixir-format msgid "You already have a join request for this activity." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:58 +#: lib/who_need_help_web/live/category_proposal_live.ex:59 #, elixir-autogen, elixir-format msgid "You already voted for this proposal." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:506 +#: lib/who_need_help_web/live/activity_live/show.ex:510 #, elixir-autogen, elixir-format msgid "You are approved, so the map can show the exact meeting coordinates." msgstr "" @@ -2335,8 +2324,8 @@ msgstr "" msgid "You are matched. Use chat to coordinate safely." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:317 -#: lib/who_need_help_web/live/request_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:610 #, elixir-autogen, elixir-format msgid "You are not allowed to do that." msgstr "" @@ -2346,7 +2335,7 @@ msgstr "" msgid "You are running the local mail adapter." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:332 +#: lib/who_need_help_web/live/request_live/new.ex:337 #, elixir-autogen, elixir-format msgid "You can also enter coordinates manually for local testing." msgstr "" @@ -2356,12 +2345,12 @@ msgstr "" msgid "You can be the first person to ask the community." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:602 +#: lib/who_need_help_web/live/request_live/show.ex:607 #, elixir-autogen, elixir-format msgid "You cannot accept your own request." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:316 +#: lib/who_need_help_web/live/activity_live/show.ex:320 #, elixir-autogen, elixir-format msgid "You cannot report your own content." msgstr "" @@ -2413,7 +2402,7 @@ msgstr "" msgid "Your profile" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:47 +#: lib/who_need_help_web/live/request_live/new.ex:52 #, elixir-autogen, elixir-format msgid "Your request is now visible to nearby helpers." msgstr "" @@ -2423,7 +2412,7 @@ msgstr "" msgid "Your requests" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:817 +#: lib/who_need_help_web/live/request_live/show.ex:822 #, elixir-autogen, elixir-format msgid "Your review is revealed only after both participants submit." msgstr "" @@ -2443,13 +2432,13 @@ msgstr "" msgid "activity message" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:42 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:261 -#: lib/who_need_help_web/live/category_proposal_live.ex:147 +#: lib/who_need_help_web/live/category_proposal_live.ex:153 #: lib/who_need_help_web/live/moderation_live.ex:317 #: lib/who_need_help_web/live/request_live/index.ex:286 #, elixir-autogen, elixir-format @@ -2458,7 +2447,7 @@ msgstr "" #: lib/who_need_help_web/live/profile_live.ex:189 #: lib/who_need_help_web/live/request_live/index.ex:323 -#: lib/who_need_help_web/live/request_live/show.ex:883 +#: lib/who_need_help_web/live/request_live/show.ex:896 #, elixir-autogen, elixir-format msgid "completed" msgstr "" @@ -2473,59 +2462,54 @@ msgstr "" msgid "for an account now." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:910 +#: lib/who_need_help_web/live/request_live/show.ex:923 #, elixir-autogen, elixir-format msgid "helper movement supported" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:911 +#: lib/who_need_help_web/live/request_live/show.ex:924 #, elixir-autogen, elixir-format msgid "no movement evidence" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:902 +#: lib/who_need_help_web/live/request_live/show.ex:915 #, elixir-autogen, elixir-format msgid "no proximity evidence" msgstr "" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 -#, elixir-autogen, elixir-format -msgid "or" -msgstr "" - -#: lib/who_need_help_web/live/activity_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:612 #, elixir-autogen, elixir-format msgid "organizer" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:756 +#: lib/who_need_help_web/live/request_live/show.ex:761 #, elixir-autogen, elixir-format msgid "participants only" msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:328 -#: lib/who_need_help_web/live/request_live/show.ex:887 +#: lib/who_need_help_web/live/request_live/show.ex:900 #, elixir-autogen, elixir-format msgid "people" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:901 +#: lib/who_need_help_web/live/request_live/show.ex:914 #, elixir-autogen, elixir-format msgid "proximity supported" msgstr "" #: lib/who_need_help_web/live/profile_live.ex:204 -#: lib/who_need_help_web/live/request_live/show.ex:891 +#: lib/who_need_help_web/live/request_live/show.ex:904 #, elixir-autogen, elixir-format msgid "rating" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:748 +#: lib/who_need_help_web/live/request_live/show.ex:753 #, elixir-autogen, elixir-format msgid "rating %{rating}" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:41 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "" @@ -2545,12 +2529,12 @@ msgstr "" msgid "unique people" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:414 -#: lib/who_need_help_web/live/activity_live/show.ex:565 -#: lib/who_need_help_web/live/activity_live/show.ex:624 +#: lib/who_need_help_web/live/activity_live/show.ex:418 +#: lib/who_need_help_web/live/activity_live/show.ex:572 +#: lib/who_need_help_web/live/activity_live/show.ex:631 #: lib/who_need_help_web/live/profile_live.ex:277 -#: lib/who_need_help_web/live/request_live/show.ex:735 -#: lib/who_need_help_web/live/request_live/show.ex:932 +#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:945 #, elixir-autogen, elixir-format msgid "unverified" msgstr "" @@ -2560,13 +2544,13 @@ msgstr "" msgid "urgent" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:413 -#: lib/who_need_help_web/live/activity_live/show.ex:564 -#: lib/who_need_help_web/live/activity_live/show.ex:623 +#: lib/who_need_help_web/live/activity_live/show.ex:417 +#: lib/who_need_help_web/live/activity_live/show.ex:571 +#: lib/who_need_help_web/live/activity_live/show.ex:630 #: lib/who_need_help_web/live/profile_live.ex:276 #: lib/who_need_help_web/live/request_live/index.ex:333 -#: lib/who_need_help_web/live/request_live/show.ex:734 -#: lib/who_need_help_web/live/request_live/show.ex:931 +#: lib/who_need_help_web/live/request_live/show.ex:739 +#: lib/who_need_help_web/live/request_live/show.ex:944 #, elixir-autogen, elixir-format msgid "verified" msgstr "" @@ -2576,18 +2560,18 @@ msgstr "" msgid "verified handovers" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:660 +#: lib/who_need_help_web/live/request_live/show.ex:665 #, elixir-autogen, elixir-format msgid "← All requests" msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:113 -#: lib/who_need_help_web/live/activity_live/show.ex:348 +#: lib/who_need_help_web/live/activity_live/new.ex:118 +#: lib/who_need_help_web/live/activity_live/show.ex:352 #, elixir-autogen, elixir-format msgid "← Back to activities" msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:152 +#: lib/who_need_help_web/live/request_live/new.ex:157 #, elixir-autogen, elixir-format msgid "← Back to requests" msgstr "" @@ -2648,15 +2632,15 @@ msgstr "" msgid "Unusual action rate" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:448 -#: lib/who_need_help_web/live/request_live/show.ex:768 +#: lib/who_need_help_web/live/activity_live/show.ex:452 +#: lib/who_need_help_web/live/request_live/show.ex:773 #, elixir-autogen, elixir-format msgid "Load earlier messages" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:273 #: lib/who_need_help_web/live/activity_live/index.ex:310 -#: lib/who_need_help_web/live/category_proposal_live.ex:162 +#: lib/who_need_help_web/live/category_proposal_live.ex:168 #: lib/who_need_help_web/live/leaderboard_live.ex:87 #: lib/who_need_help_web/live/moderation_live.ex:384 #: lib/who_need_help_web/live/moderation_live.ex:419 @@ -2671,9 +2655,9 @@ msgid "Load more" msgstr "" #: lib/who_need_help_web/live/activity_live/index.ex:284 -#: lib/who_need_help_web/live/activity_live/show.ex:498 +#: lib/who_need_help_web/live/activity_live/show.ex:502 #: lib/who_need_help_web/live/request_live/index.ex:314 -#: lib/who_need_help_web/live/request_live/show.ex:849 +#: lib/who_need_help_web/live/request_live/show.ex:862 #, elixir-autogen, elixir-format msgid "The map is unavailable in this browser. Request details remain usable." msgstr "" @@ -2694,19 +2678,19 @@ msgstr "" msgid "Could not complete moderation. Please try again." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:336 -#: lib/who_need_help_web/live/category_proposal_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:621 +#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/category_proposal_live.ex:61 +#: lib/who_need_help_web/live/request_live/show.ex:626 #, elixir-autogen, elixir-format msgid "Could not complete the action. Please try again." msgstr "" -#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/activity_live/new.ex:110 #, elixir-autogen, elixir-format msgid "Could not publish the activity. Please try again." msgstr "" -#: lib/who_need_help_web/live/request_live/new.ex:144 +#: lib/who_need_help_web/live/request_live/new.ex:149 #, elixir-autogen, elixir-format msgid "Could not publish the request. Please try again." msgstr "" @@ -2721,32 +2705,32 @@ msgstr "" msgid "JavaScript is required to confirm this protected email link." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:594 +#: lib/who_need_help_web/live/request_live/show.ex:599 #, elixir-autogen, elixir-format msgid "Last update: %{time}" msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:617 +#: lib/who_need_help_web/live/request_live/show.ex:622 #, elixir-autogen, elixir-format msgid "Live location sharing is not active." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:592 +#: lib/who_need_help_web/live/request_live/show.ex:597 #, elixir-autogen, elixir-format msgid "Shared location" msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:329 +#: lib/who_need_help_web/live/activity_live/show.ex:333 #, elixir-autogen, elixir-format msgid "The organizer cannot leave the activity." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:326 +#: lib/who_need_help_web/live/activity_live/show.ex:330 #, elixir-autogen, elixir-format msgid "The organizer is already attending." msgstr "" -#: lib/who_need_help_web/controllers/user_registration_controller.ex:60 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:58 #, elixir-autogen, elixir-format msgid "The registration form is invalid." msgstr "" @@ -2766,22 +2750,22 @@ msgstr "" msgid "The sign-in form is invalid." msgstr "" -#: lib/who_need_help_web/live/activity_live/show.ex:323 +#: lib/who_need_help_web/live/activity_live/show.ex:327 #, elixir-autogen, elixir-format msgid "This activity is no longer available." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:616 +#: lib/who_need_help_web/live/request_live/show.ex:621 #, elixir-autogen, elixir-format msgid "This match is no longer active." msgstr "" -#: lib/who_need_help_web/live/category_proposal_live.ex:57 +#: lib/who_need_help_web/live/category_proposal_live.ex:58 #, elixir-autogen, elixir-format msgid "This proposal is no longer available." msgstr "" -#: lib/who_need_help_web/live/request_live/show.ex:614 +#: lib/who_need_help_web/live/request_live/show.ex:619 #, elixir-autogen, elixir-format msgid "This request has expired." msgstr "" @@ -2796,12 +2780,12 @@ msgstr "" msgid "Your secure email link is ready. Continue to sign in." msgstr "" -#: lib/who_need_help_web/controllers/user_settings_controller.ex:128 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:130 #, elixir-autogen, elixir-format msgid "The confirmation email could not be sent. Please try again." msgstr "" -#: lib/who_need_help_web/controllers/user_registration_controller.ex:109 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:107 #, elixir-autogen, elixir-format msgid "If this address can be registered or signed in, login instructions will arrive shortly." msgstr "If this address can be registered or signed in, login instructions will arrive shortly." @@ -2815,3 +2799,185 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Too many email-change requests in the configured time window." msgstr "Too many email-change requests in the configured time window." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:32 +#, elixir-autogen, elixir-format +msgid "A Google account is connected. You can use it to sign in." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:207 +#, elixir-autogen, elixir-format +msgid "A different Google account is already connected." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:17 +#, elixir-autogen, elixir-format +msgid "A password is not required. We will email a one-time confirmation link. You can add a password later in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:170 +#, elixir-autogen, elixir-format +msgid "An account with this email already exists. Sign in by email or password, then connect Google in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:37 +#, elixir-autogen, elixir-format +msgid "Confirm that you are 18 or older and accept the safety rules first." +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:49 +#, elixir-autogen, elixir-format +msgid "Connect Google account" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Connect Google only after signing in here. We never merge accounts automatically by matching email addresses." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "Create account and email me a link" +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:99 +#, elixir-autogen, elixir-format +msgid "Email me a sign-in link" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:215 +#, elixir-autogen, elixir-format +msgid "Google account connection session is invalid." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:97 +#, elixir-autogen, elixir-format +msgid "Google did not provide a verified email address." +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:29 +#, elixir-autogen, elixir-format +msgid "Google sign-in" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:197 +#, elixir-autogen, elixir-format +msgid "Google sign-in connected." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:123 +#, elixir-autogen, elixir-format +msgid "Google sign-in could not be started." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:102 +#, elixir-autogen, elixir-format +msgid "Google sign-in failed. Please try again." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:128 +#, elixir-autogen, elixir-format +msgid "Google sign-in is not configured yet." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:87 +#, elixir-autogen, elixir-format +msgid "Google sign-in session expired. Please start again." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:91 +#, elixir-autogen, elixir-format +msgid "Google sign-in session is invalid." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:47 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Google sign-in will be available after the operator configures it." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 +#, elixir-autogen, elixir-format +msgid "Sending link..." +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:74 +#, elixir-autogen, elixir-format +msgid "Sign in with Google" +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Sign up with Google" +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:202 +#, elixir-autogen, elixir-format +msgid "That Google account belongs to another local account." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:45 +#, elixir-autogen, elixir-format, fuzzy +msgid "The Google registration form is invalid." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:260 +#, elixir-autogen, elixir-format +msgid "This Google account cannot be used right now." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:144 +#, elixir-autogen, elixir-format +msgid "This Google account is not connected yet. Create an account or sign in by email and connect Google in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:109 +#, elixir-autogen, elixir-format +msgid "This works only if you previously added a password in account settings." +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#, elixir-autogen, elixir-format +msgid "Use a password instead" +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "We will send a one-time sign-in link. No password is required." +msgstr "" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:163 +#, elixir-autogen, elixir-format +msgid "Your account was created with Google." +msgstr "" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:51 +#, elixir-autogen, elixir-format, fuzzy +msgid "or sign up with email" +msgstr "" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 +#, elixir-autogen, elixir-format +msgid "or use email" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Disconnect Google" +msgstr "Disconnect Google" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Disconnect Google sign-in from this account?" +msgstr "Disconnect Google sign-in from this account?" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:60 +#, elixir-autogen, elixir-format +msgid "Google sign-in disconnected." +msgstr "Google sign-in disconnected." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:65 +#, elixir-autogen, elixir-format +msgid "No Google account is connected." +msgstr "No Google account is connected." diff --git a/priv/gettext/ru/LC_MESSAGES/default.po b/priv/gettext/ru/LC_MESSAGES/default.po index 749fc6b..2bd4d4c 100644 --- a/priv/gettext/ru/LC_MESSAGES/default.po +++ b/priv/gettext/ru/LC_MESSAGES/default.po @@ -17,7 +17,7 @@ msgstr "" msgid "Account settings" msgstr "Настройки аккаунта" -#: lib/who_need_help_web/components/core_components.ex:393 +#: lib/who_need_help_web/components/core_components.ex:446 #, elixir-autogen, elixir-format msgid "Actions" msgstr "Действия" @@ -25,7 +25,7 @@ msgstr "Действия" #: lib/who_need_help_web/components/layouts.ex:70 #: lib/who_need_help_web/components/layouts.ex:119 #: lib/who_need_help_web/controllers/page_html/home.html.heex:21 -#: lib/who_need_help_web/live/request_live/new.ex:22 +#: lib/who_need_help_web/live/request_live/new.ex:23 #, elixir-autogen, elixir-format msgid "Ask for help" msgstr "Попросить о помощи" @@ -108,7 +108,7 @@ msgstr "Нет соединения с интернетом" msgid "close" msgstr "закрыть" -#: lib/who_need_help_web/live/activity_live/show.ex:363 +#: lib/who_need_help_web/live/activity_live/show.ex:367 #, elixir-autogen, elixir-format msgid "%{approved}/%{capacity} approved" msgstr "Одобрено: %{approved}/%{capacity}" @@ -118,37 +118,37 @@ msgstr "Одобрено: %{approved}/%{capacity}" msgid "%{count} community votes" msgstr "Голосов сообщества: %{count}" -#: lib/who_need_help_web/live/request_live/show.ex:745 +#: lib/who_need_help_web/live/request_live/show.ex:750 #, elixir-autogen, elixir-format msgid "%{count} unique people" msgstr "%{count} уникальных людей" -#: lib/who_need_help_web/live/request_live/show.ex:829 +#: lib/who_need_help_web/live/request_live/show.ex:842 #, elixir-autogen, elixir-format msgid "1 — unsafe" msgstr "1 — небезопасно" -#: lib/who_need_help_web/live/request_live/show.ex:828 +#: lib/who_need_help_web/live/request_live/show.ex:841 #, elixir-autogen, elixir-format msgid "2 — poor" msgstr "2 — плохо" -#: lib/who_need_help_web/live/request_live/show.ex:827 +#: lib/who_need_help_web/live/request_live/show.ex:840 #, elixir-autogen, elixir-format msgid "3 — okay" msgstr "3 — нормально" -#: lib/who_need_help_web/live/request_live/show.ex:826 +#: lib/who_need_help_web/live/request_live/show.ex:839 #, elixir-autogen, elixir-format msgid "4 — good" msgstr "4 — хорошо" -#: lib/who_need_help_web/live/request_live/show.ex:825 +#: lib/who_need_help_web/live/request_live/show.ex:838 #, elixir-autogen, elixir-format msgid "5 — excellent" msgstr "5 — отлично" -#: lib/who_need_help_web/controllers/user_settings_controller.ex:118 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:120 #, elixir-autogen, elixir-format msgid "A link to confirm your email change has been sent to the new address." msgstr "" @@ -162,13 +162,13 @@ msgstr "" "Сигнал требует проверки человеком; отсутствие необязательного отслеживания " "не является доказательством злоупотребления." -#: lib/who_need_help_web/live/request_live/show.ex:859 +#: lib/who_need_help_web/live/request_live/show.ex:872 #, elixir-autogen, elixir-format msgid "Accept only if you can complete this safely and for free." msgstr "" "Принимайте заявку, только если можете безопасно и бесплатно её выполнить." -#: lib/who_need_help_web/live/request_live/show.ex:633 +#: lib/who_need_help_web/live/request_live/show.ex:638 #, elixir-autogen, elixir-format msgid "Accepted" msgstr "Принято" @@ -189,7 +189,7 @@ msgstr "Аккаунт и язык" msgid "Accounts" msgstr "Аккаунты" -#: lib/who_need_help_web/live/request_live/show.ex:855 +#: lib/who_need_help_web/live/request_live/show.ex:868 #, elixir-autogen, elixir-format msgid "Action center" msgstr "Центр действий" @@ -217,7 +217,7 @@ msgstr "" "походов согласуйте маршрут и меры безопасности и никогда не публикуйте " "домашние адреса, коды доступа или документы для поездки." -#: lib/who_need_help_web/live/activity_live/new.ex:163 +#: lib/who_need_help_web/live/activity_live/new.ex:168 #, elixir-autogen, elixir-format msgid "Activity details" msgstr "Детали активности" @@ -228,12 +228,12 @@ msgid "Activity hidden." msgstr "Активность скрыта." #: lib/who_need_help_web/live/activity_live/show.ex:18 -#: lib/who_need_help_web/live/activity_live/show.ex:177 +#: lib/who_need_help_web/live/activity_live/show.ex:181 #, elixir-autogen, elixir-format msgid "Activity is unavailable." msgstr "Активность недоступна." -#: lib/who_need_help_web/live/activity_live/new.ex:36 +#: lib/who_need_help_web/live/activity_live/new.ex:41 #, elixir-autogen, elixir-format msgid "Activity published. You approve every participant." msgstr "Активность опубликована. Вы одобряете каждого участника." @@ -244,7 +244,7 @@ msgid "Activity restored." msgstr "Активность восстановлена." #: lib/who_need_help_web/live/activity_live/index.ex:210 -#: lib/who_need_help_web/live/activity_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Activity type" msgstr "Тип активности" @@ -275,7 +275,7 @@ msgstr "Все категории" msgid "Already registered?" msgstr "Уже зарегистрированы?" -#: lib/who_need_help_web/live/request_live/show.ex:603 +#: lib/who_need_help_web/live/request_live/show.ex:608 #, elixir-autogen, elixir-format msgid "Another helper already accepted this request." msgstr "Другой помощник уже принял эту заявку." @@ -285,60 +285,60 @@ msgstr "Другой помощник уже принял эту заявку." msgid "Any urgency" msgstr "Любая срочность" -#: lib/who_need_help_web/live/activity_live/show.ex:524 +#: lib/who_need_help_web/live/activity_live/show.ex:528 #, elixir-autogen, elixir-format msgid "Approval pending" msgstr "Ожидает одобрения" -#: lib/who_need_help_web/live/activity_live/show.ex:574 +#: lib/who_need_help_web/live/activity_live/show.ex:581 #, elixir-autogen, elixir-format msgid "Approve" msgstr "Одобрить" -#: lib/who_need_help_web/live/activity_live/show.ex:435 +#: lib/who_need_help_web/live/activity_live/show.ex:439 #, elixir-autogen, elixir-format msgid "Approved group chat" msgstr "Чат одобренной группы" -#: lib/who_need_help_web/live/activity_live/show.ex:600 +#: lib/who_need_help_web/live/activity_live/show.ex:607 #, elixir-autogen, elixir-format msgid "Approved participants" msgstr "Одобренные участники" #: lib/who_need_help_web/controllers/page_html/home.html.heex:51 -#: lib/who_need_help_web/live/activity_live/new.ex:258 -#: lib/who_need_help_web/live/request_live/new.ex:306 +#: lib/who_need_help_web/live/activity_live/new.ex:263 +#: lib/who_need_help_web/live/request_live/new.ex:311 #, elixir-autogen, elixir-format msgid "Approximate area" msgstr "Примерный район" -#: lib/who_need_help_web/live/activity_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Approximate public meeting area" msgstr "Примерный публичный район встречи" #: lib/who_need_help_web/live/profile_live.ex:167 -#: lib/who_need_help_web/live/request_live/show.ex:639 +#: lib/who_need_help_web/live/request_live/show.ex:644 #, elixir-autogen, elixir-format msgid "Approximate publicly" msgstr "Публично — приблизительно" -#: lib/who_need_help_web/live/request_live/new.ex:314 +#: lib/who_need_help_web/live/request_live/new.ex:319 #, elixir-autogen, elixir-format msgid "Approximate publicly (recommended)" msgstr "Показывать приблизительно (рекомендуется)" -#: lib/who_need_help_web/live/request_live/show.ex:702 +#: lib/who_need_help_web/live/request_live/show.ex:707 #, elixir-autogen, elixir-format msgid "Area:" msgstr "Район:" -#: lib/who_need_help_web/live/request_live/new.ex:154 +#: lib/who_need_help_web/live/request_live/new.ex:159 #, elixir-autogen, elixir-format msgid "Ask for urgent help" msgstr "Попросить срочную помощь" -#: lib/who_need_help_web/live/category_proposal_live.ex:92 +#: lib/who_need_help_web/live/category_proposal_live.ex:98 #, elixir-autogen, elixir-format msgid "Bicycle puncture" msgstr "Прокол колеса велосипеда" @@ -358,12 +358,12 @@ msgstr "" msgid "Block and report" msgstr "Блокировка и жалоба" -#: lib/who_need_help_web/live/activity_live/show.ex:641 +#: lib/who_need_help_web/live/activity_live/show.ex:648 #, elixir-autogen, elixir-format msgid "Block organizer" msgstr "Заблокировать организатора" -#: lib/who_need_help_web/live/request_live/show.ex:1062 +#: lib/who_need_help_web/live/request_live/show.ex:1075 #, elixir-autogen, elixir-format msgid "Block this user" msgstr "Заблокировать пользователя" @@ -379,7 +379,7 @@ msgid "Both confirm and the helper enters a one-time code." msgstr "" "Оба участника подтверждают завершение, а помощник вводит одноразовый код." -#: lib/who_need_help_web/live/request_live/new.ex:118 +#: lib/who_need_help_web/live/request_live/new.ex:123 #, elixir-autogen, elixir-format msgid "Briefly describe the help you need" msgstr "Кратко опишите, какая помощь вам нужна" @@ -399,33 +399,33 @@ msgstr "Отчёт о разработке" msgid "COMMUNITY TRUST" msgstr "ДОВЕРИЕ СООБЩЕСТВА" -#: lib/who_need_help_web/live/category_proposal_live.ex:80 +#: lib/who_need_help_web/live/category_proposal_live.ex:81 #, elixir-autogen, elixir-format msgid "COMMUNITY-DRIVEN TAXONOMY" msgstr "КАТЕГОРИИ ОТ СООБЩЕСТВА" -#: lib/who_need_help_web/live/activity_live/show.ex:594 +#: lib/who_need_help_web/live/activity_live/show.ex:601 #, elixir-autogen, elixir-format msgid "Cancel activity" msgstr "Отменить активность" -#: lib/who_need_help_web/live/request_live/show.ex:873 -#: lib/who_need_help_web/live/request_live/show.ex:967 +#: lib/who_need_help_web/live/request_live/show.ex:886 +#: lib/who_need_help_web/live/request_live/show.ex:980 #, elixir-autogen, elixir-format msgid "Cancel request" msgstr "Отменить заявку" #: lib/who_need_help_web/live/activity_live/index.ex:168 -#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/activity_live/show.ex:344 #: lib/who_need_help_web/live/request_live/index.ex:184 #: lib/who_need_help_web/live/request_live/index.ex:185 -#: lib/who_need_help_web/live/request_live/show.ex:636 +#: lib/who_need_help_web/live/request_live/show.ex:641 #, elixir-autogen, elixir-format msgid "Cancelled" msgstr "Отменено" #: lib/who_need_help_web/live/request_live/index.ex:225 -#: lib/who_need_help_web/live/request_live/new.ex:180 +#: lib/who_need_help_web/live/request_live/new.ex:185 #, elixir-autogen, elixir-format msgid "Category" msgstr "Категория" @@ -435,13 +435,13 @@ msgstr "Категория" msgid "Category created and proposal approved." msgstr "Категория создана и предложение одобрено." -#: lib/who_need_help_web/live/request_live/new.ex:211 -#: lib/who_need_help_web/live/request_live/show.ex:691 +#: lib/who_need_help_web/live/request_live/new.ex:216 +#: lib/who_need_help_web/live/request_live/show.ex:696 #, elixir-autogen, elixir-format msgid "Category details" msgstr "Детали категории" -#: lib/who_need_help_web/live/category_proposal_live.ex:66 +#: lib/who_need_help_web/live/category_proposal_live.ex:67 #: lib/who_need_help_web/live/moderation_live.ex:466 #, elixir-autogen, elixir-format msgid "Category proposals" @@ -458,7 +458,7 @@ msgid "Change role" msgstr "Изменить роль" #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:21 -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:47 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:107 #, elixir-autogen, elixir-format msgid "Changing..." msgstr "Изменение…" @@ -469,31 +469,31 @@ msgid "Choose a category, urgency, and safe location visibility." msgstr "" "Выберите категорию, срочность и безопасный уровень видимости геопозиции." -#: lib/who_need_help_web/live/activity_live/new.ex:140 +#: lib/who_need_help_web/live/activity_live/new.ex:145 #, elixir-autogen, elixir-format msgid "Choose activity" msgstr "Выберите активность" -#: lib/who_need_help_web/live/request_live/new.ex:181 +#: lib/who_need_help_web/live/request_live/new.ex:186 #, elixir-autogen, elixir-format msgid "Choose category" msgstr "Выберите категорию" -#: lib/who_need_help_web/live/activity_live/new.ex:175 -#: lib/who_need_help_web/live/activity_live/new.ex:202 -#: lib/who_need_help_web/live/request_live/new.ex:223 -#: lib/who_need_help_web/live/request_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:180 +#: lib/who_need_help_web/live/activity_live/new.ex:207 +#: lib/who_need_help_web/live/request_live/new.ex:228 +#: lib/who_need_help_web/live/request_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Choose…" msgstr "Выберите…" -#: lib/who_need_help_web/live/activity_live/new.ex:251 +#: lib/who_need_help_web/live/activity_live/new.ex:256 #, elixir-autogen, elixir-format msgid "City centre, near the main entrance" msgstr "Центр города, рядом с главным входом" -#: lib/who_need_help_web/live/activity_live/show.ex:654 -#: lib/who_need_help_web/live/request_live/show.ex:1078 +#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/request_live/show.ex:1096 #, elixir-autogen, elixir-format msgid "Clear" msgstr "Очистить" @@ -503,7 +503,7 @@ msgstr "Очистить" msgid "Close" msgstr "Закрыть" -#: lib/who_need_help_web/live/activity_live/new.ex:220 +#: lib/who_need_help_web/live/activity_live/new.ex:225 #, elixir-autogen, elixir-format msgid "Coffee and conversation" msgstr "Кофе и разговоры" @@ -515,7 +515,7 @@ msgstr "" "Кофе, кино, прогулки и походы отделены от срочной помощи и никогда не влияют" " на рейтинг помощника." -#: lib/who_need_help_web/live/request_live/show.ex:835 +#: lib/who_need_help_web/live/request_live/show.ex:848 #, elixir-autogen, elixir-format msgid "Comment (optional)" msgstr "Комментарий (необязательно)" @@ -526,19 +526,19 @@ msgid "Community helpers" msgstr "Помощники сообщества" #: lib/who_need_help_web/live/activity_live/index.ex:262 -#: lib/who_need_help_web/live/activity_live/show.ex:392 -#: lib/who_need_help_web/live/activity_live/show.ex:546 -#: lib/who_need_help_web/live/activity_live/show.ex:603 +#: lib/who_need_help_web/live/activity_live/show.ex:396 +#: lib/who_need_help_web/live/activity_live/show.ex:553 +#: lib/who_need_help_web/live/activity_live/show.ex:610 #: lib/who_need_help_web/live/request_live/index.ex:287 #, elixir-autogen, elixir-format msgid "Community member" msgstr "Член сообщества" #: lib/who_need_help_web/live/activity_live/index.ex:167 -#: lib/who_need_help_web/live/activity_live/show.ex:339 +#: lib/who_need_help_web/live/activity_live/show.ex:343 #: lib/who_need_help_web/live/request_live/index.ex:182 #: lib/who_need_help_web/live/request_live/index.ex:183 -#: lib/who_need_help_web/live/request_live/show.ex:635 +#: lib/who_need_help_web/live/request_live/show.ex:640 #, elixir-autogen, elixir-format msgid "Completed" msgstr "Завершено" @@ -553,24 +553,24 @@ msgstr "Подтвердить и войти только сейчас" msgid "Confirm and stay logged in" msgstr "Подтвердить и оставаться в системе" -#: lib/who_need_help_web/live/request_live/show.ex:947 +#: lib/who_need_help_web/live/request_live/show.ex:960 #, elixir-autogen, elixir-format msgid "Confirm my part is complete" msgstr "Подтвердить выполнение своей части" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:42 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:102 #, elixir-autogen, elixir-format msgid "Confirm new password" msgstr "Подтвердите новый пароль" -#: lib/who_need_help_web/live/category_proposal_live.ex:51 -#: lib/who_need_help_web/live/request_live/show.ex:611 +#: lib/who_need_help_web/live/category_proposal_live.ex:52 +#: lib/who_need_help_web/live/request_live/show.ex:616 #, elixir-autogen, elixir-format msgid "Confirm your account and ensure it is active first." msgstr "Подтвердите свою учетную запись и убедитесь, что она активна." -#: lib/who_need_help_web/live/activity_live/new.ex:100 -#: lib/who_need_help_web/live/request_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/request_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Confirm your email and ensure your account is active before publishing." msgstr "" @@ -595,17 +595,12 @@ msgid "Create a clear plan in a public place." msgstr "Создайте четкий план в общественном месте." #: lib/who_need_help_web/live/activity_live/index.ex:188 -#: lib/who_need_help_web/live/activity_live/new.ex:11 +#: lib/who_need_help_web/live/activity_live/new.ex:12 #, elixir-autogen, elixir-format msgid "Create activity" msgstr "Создать активность" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:59 -#, elixir-autogen, elixir-format -msgid "Create an account" -msgstr "Создать аккаунт" - -#: lib/who_need_help_web/live/activity_live/new.ex:115 +#: lib/who_need_help_web/live/activity_live/new.ex:120 #, elixir-autogen, elixir-format msgid "Create an activity" msgstr "Создать активность" @@ -620,18 +615,13 @@ msgstr "Создать категорию" msgid "Create urgent request" msgstr "Создать срочную заявку" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:58 -#, elixir-autogen, elixir-format -msgid "Creating account..." -msgstr "Создание аккаунта..." - -#: lib/who_need_help_web/live/activity_live/show.ex:663 +#: lib/who_need_help_web/live/activity_live/show.ex:675 #, elixir-autogen, elixir-format msgid "Dangerous plan" msgstr "Опасный план" #: lib/who_need_help_web/live/moderation_live.ex:248 -#: lib/who_need_help_web/live/request_live/show.ex:1086 +#: lib/who_need_help_web/live/request_live/show.ex:1104 #, elixir-autogen, elixir-format msgid "Dangerous request" msgstr "Опасный запрос" @@ -653,7 +643,7 @@ msgstr "" "Решения и доступ к материалам жалоб на переписку записываются в журнал " "аудита." -#: lib/who_need_help_web/live/activity_live/show.ex:581 +#: lib/who_need_help_web/live/activity_live/show.ex:588 #, elixir-autogen, elixir-format msgid "Decline" msgstr "Отклонить" @@ -663,7 +653,7 @@ msgstr "Отклонить" msgid "Default location visibility" msgstr "Видимость местоположения по умолчанию" -#: lib/who_need_help_web/live/activity_live/new.ex:227 +#: lib/who_need_help_web/live/activity_live/new.ex:232 #, elixir-autogen, elixir-format msgid "Describe the public plan, approximate duration, and who might enjoy it." msgstr "" @@ -677,7 +667,7 @@ msgstr "" msgid "Dismissed" msgstr "Отклонено" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:34 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -696,14 +686,14 @@ msgstr "" msgid "Don't have an account?" msgstr "Нет аккаунта?" -#: lib/who_need_help_web/live/request_live/show.ex:815 +#: lib/who_need_help_web/live/request_live/show.ex:820 #, elixir-autogen, elixir-format msgid "Double-blind review" msgstr "Двусторонний скрытый отзыв" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:25 -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 #: lib/who_need_help_web/controllers/user_session_html/new.html.heex:89 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:122 #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:15 #, elixir-autogen, elixir-format msgid "Email" @@ -745,7 +735,7 @@ msgstr "Описание на английском" msgid "English name" msgstr "Название на английском" -#: lib/who_need_help_web/live/request_live/show.ex:992 +#: lib/who_need_help_web/live/request_live/show.ex:1005 #, elixir-autogen, elixir-format msgid "Enter handover code" msgstr "Введите код передачи" @@ -755,7 +745,7 @@ msgstr "Введите код передачи" msgid "Exact address after matching" msgstr "Точный адрес после назначения помощника" -#: lib/who_need_help_web/live/activity_live/new.ex:274 +#: lib/who_need_help_web/live/activity_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Exact coordinates are shown only to participants you approve." msgstr "Точные координаты показаны только участникам, которых вы одобряете." @@ -765,29 +755,29 @@ msgstr "Точные координаты показаны только учас msgid "Exact for active match" msgstr "Точно — для активной заявки" -#: lib/who_need_help_web/live/request_live/new.ex:316 -#: lib/who_need_help_web/live/request_live/show.ex:641 +#: lib/who_need_help_web/live/request_live/new.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:646 #, elixir-autogen, elixir-format msgid "Exact only for active match" msgstr "Точно только для активной заявки" #: lib/who_need_help_web/live/profile_live.ex:169 -#: lib/who_need_help_web/live/request_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:647 #, elixir-autogen, elixir-format msgid "Exact publicly" msgstr "Публично — точно" -#: lib/who_need_help_web/live/request_live/new.ex:317 +#: lib/who_need_help_web/live/request_live/new.ex:322 #, elixir-autogen, elixir-format msgid "Exact publicly — explicit consent" msgstr "Публично — точно, с явным согласием" -#: lib/who_need_help_web/live/request_live/show.ex:710 +#: lib/who_need_help_web/live/request_live/show.ex:715 #, elixir-autogen, elixir-format msgid "Expires:" msgstr "Истекает:" -#: lib/who_need_help_web/live/request_live/new.ex:274 +#: lib/who_need_help_web/live/request_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Explain what is already arranged and what the volunteer needs to do." msgstr "Объясните, что уже сделано и что нужно сделать волонтеру." @@ -797,9 +787,9 @@ msgstr "Объясните, что уже сделано и что нужно с msgid "Find people to join" msgstr "Найти компанию" -#: lib/who_need_help_web/live/activity_live/show.ex:664 +#: lib/who_need_help_web/live/activity_live/show.ex:676 #: lib/who_need_help_web/live/moderation_live.ex:250 -#: lib/who_need_help_web/live/request_live/show.ex:1088 +#: lib/who_need_help_web/live/request_live/show.ex:1106 #, elixir-autogen, elixir-format msgid "Fraud" msgstr "Мошенничество" @@ -821,7 +811,7 @@ msgstr "" "Подтверждение через GitHub недоступно, пока оператор не настроит учётные " "данные OAuth. Ссылка, введённая вручную, не считается подтверждённой." -#: lib/who_need_help_web/live/request_live/show.ex:975 +#: lib/who_need_help_web/live/request_live/show.ex:988 #, elixir-autogen, elixir-format msgid "HANDOVER CODE FOR THE HELPER" msgstr "КОД ПЕРЕДАЧИ ДЛЯ ПОМОЩНИКА" @@ -836,9 +826,9 @@ msgstr "Имя пользователя (необязательно)" msgid "Handover code verified." msgstr "Код передачи подтверждён." -#: lib/who_need_help_web/live/activity_live/show.ex:662 +#: lib/who_need_help_web/live/activity_live/show.ex:674 #: lib/who_need_help_web/live/moderation_live.ex:249 -#: lib/who_need_help_web/live/request_live/show.ex:1087 +#: lib/who_need_help_web/live/request_live/show.ex:1105 #, elixir-autogen, elixir-format msgid "Harassment" msgstr "Домогательства" @@ -862,7 +852,7 @@ msgstr "" msgid "Helper" msgstr "Помощник" -#: lib/who_need_help_web/live/request_live/show.ex:878 +#: lib/who_need_help_web/live/request_live/show.ex:891 #, elixir-autogen, elixir-format msgid "Helper: %{name}" msgstr "Помощник: %{name}" @@ -913,10 +903,10 @@ msgstr "" "\n" "Если вы не запрашивали это письмо, просто проигнорируйте его." -#: lib/who_need_help_web/live/activity_live/new.ex:259 +#: lib/who_need_help_web/live/activity_live/new.ex:264 #: lib/who_need_help_web/live/profile_live.ex:166 -#: lib/who_need_help_web/live/request_live/new.ex:315 -#: lib/who_need_help_web/live/request_live/show.ex:640 +#: lib/who_need_help_web/live/request_live/new.ex:320 +#: lib/who_need_help_web/live/request_live/show.ex:645 #, elixir-autogen, elixir-format msgid "Hidden" msgstr "Скрыто" @@ -942,18 +932,19 @@ msgstr "Скрыть заявку" msgid "Human decisions retained" msgstr "Решения остаются за человеком" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:48 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:39 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:85 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "Мне 18 лет и я принимаю правила безопасности." #: lib/who_need_help_web/controllers/page_html/home.html.heex:56 -#: lib/who_need_help_web/live/request_live/show.ex:862 +#: lib/who_need_help_web/live/request_live/show.ex:875 #, elixir-autogen, elixir-format msgid "I can help" msgstr "Я могу помочь" -#: lib/who_need_help_web/live/request_live/new.ex:127 +#: lib/who_need_help_web/live/request_live/new.ex:132 #, elixir-autogen, elixir-format msgid "I confirm there is no immediate danger, people and vehicles are away from active traffic or emergency services have already been contacted, and the requested task is lawful." msgstr "" @@ -961,7 +952,7 @@ msgstr "" "находятся вне активного движения либо экстренные службы уже вызваны, а " "запрашиваемая помощь законна." -#: lib/who_need_help_web/live/request_live/new.ex:121 +#: lib/who_need_help_web/live/request_live/new.ex:126 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, medical advice request, controlled substance, or request to buy medicine on my behalf." msgstr "" @@ -969,14 +960,14 @@ msgstr "" "консультации или контролируемого вещества и не просьба купить лекарство от " "моего имени." -#: lib/who_need_help_web/live/request_live/new.ex:133 +#: lib/who_need_help_web/live/request_live/new.ex:138 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, there is no immediate danger, and the requested task is lawful." msgstr "" "Я подтверждаю, что это не экстренная ситуация, непосредственной опасности " "нет, а запрашиваемая помощь законна." -#: lib/who_need_help_web/live/activity_live/new.ex:298 +#: lib/who_need_help_web/live/activity_live/new.ex:303 #, elixir-autogen, elixir-format msgid "I will approve participants individually, keep sensitive details in the approved group, and use an appropriate public first meeting point." msgstr "" @@ -996,14 +987,14 @@ msgstr "" "Если этот адрес электронной почты есть в нашей системе, вскоре вы получите " "инструкции для входа." -#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/activity_live/show.ex:678 #: lib/who_need_help_web/live/moderation_live.ex:253 -#: lib/who_need_help_web/live/request_live/show.ex:1091 +#: lib/who_need_help_web/live/request_live/show.ex:1109 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "Выдача себя за другого человека" -#: lib/who_need_help_web/live/request_live/show.ex:1027 +#: lib/who_need_help_web/live/request_live/show.ex:1040 #, elixir-autogen, elixir-format msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops." msgstr "" @@ -1013,7 +1004,7 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:180 #: lib/who_need_help_web/live/request_live/index.ex:181 -#: lib/who_need_help_web/live/request_live/show.ex:634 +#: lib/who_need_help_web/live/request_live/show.ex:639 #, elixir-autogen, elixir-format msgid "In progress" msgstr "В процессе" @@ -1033,51 +1024,51 @@ msgstr "Внутренняя заметка" msgid "Invalid email or password" msgstr "Неверная электронная почта или пароль" -#: lib/who_need_help_web/live/activity_live/show.ex:314 +#: lib/who_need_help_web/live/activity_live/show.ex:318 #, elixir-autogen, elixir-format msgid "Join requests are closed." msgstr "Приём заявок на участие закрыт." -#: lib/who_need_help_web/live/activity_live/show.ex:380 +#: lib/who_need_help_web/live/activity_live/show.ex:384 #, elixir-autogen, elixir-format msgid "Join requests close" msgstr "Приём заявок завершается" -#: lib/who_need_help_web/live/activity_live/new.ex:239 +#: lib/who_need_help_web/live/activity_live/new.ex:244 #, elixir-autogen, elixir-format msgid "Join requests close (UTC)" msgstr "Завершение приёма заявок (UTC)" -#: lib/who_need_help_web/live/activity_live/new.ex:282 -#: lib/who_need_help_web/live/request_live/new.ex:340 +#: lib/who_need_help_web/live/activity_live/new.ex:287 +#: lib/who_need_help_web/live/request_live/new.ex:345 #, elixir-autogen, elixir-format msgid "Latitude" msgstr "Широта" -#: lib/who_need_help_web/live/activity_live/show.ex:534 +#: lib/who_need_help_web/live/activity_live/show.ex:541 #, elixir-autogen, elixir-format msgid "Leave activity" msgstr "Покинуть активность" -#: lib/who_need_help_web/live/request_live/show.ex:1025 +#: lib/who_need_help_web/live/request_live/show.ex:1038 #, elixir-autogen, elixir-format msgid "Live location" msgstr "Геопозиция в реальном времени" #: lib/who_need_help_web/controllers/page_html/safety.html.heex:51 -#: lib/who_need_help_web/live/activity_live/show.ex:503 +#: lib/who_need_help_web/live/activity_live/show.ex:507 #, elixir-autogen, elixir-format msgid "Location privacy" msgstr "Конфиденциальность геопозиции" -#: lib/who_need_help_web/live/request_live/show.ex:914 +#: lib/who_need_help_web/live/request_live/show.ex:927 #, elixir-autogen, elixir-format msgid "Location sharing is optional; missing evidence is not proof of abuse." msgstr "" "Передача геопозиции необязательна; отсутствие таких данных не является " "доказательством злоупотребления." -#: lib/who_need_help_web/live/request_live/new.ex:312 +#: lib/who_need_help_web/live/request_live/new.ex:317 #, elixir-autogen, elixir-format msgid "Location visibility" msgstr "Видимость геопозиции" @@ -1087,12 +1078,12 @@ msgstr "Видимость геопозиции" msgid "Location-supported people" msgstr "Люди с подтверждением геопозицией" -#: lib/who_need_help_web/live/request_live/show.ex:717 +#: lib/who_need_help_web/live/request_live/show.ex:722 #, elixir-autogen, elixir-format msgid "Location:" msgstr "Геопозиция:" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:102 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:136 #, elixir-autogen, elixir-format msgid "Log in and stay logged in" msgstr "Войти и оставаться в системе" @@ -1102,23 +1093,18 @@ msgstr "Войти и оставаться в системе" msgid "Log in instructions" msgstr "Инструкции для входа" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:139 #, elixir-autogen, elixir-format msgid "Log in only this time" msgstr "Войти только сейчас" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 -#, elixir-autogen, elixir-format -msgid "Log in with email" -msgstr "Войти с email" - #: lib/who_need_help_web/controllers/user_session_controller.ex:117 #, elixir-autogen, elixir-format msgid "Logged out successfully." msgstr "Вы успешно вышли." -#: lib/who_need_help_web/live/activity_live/new.ex:289 -#: lib/who_need_help_web/live/request_live/new.ex:347 +#: lib/who_need_help_web/live/activity_live/new.ex:294 +#: lib/who_need_help_web/live/request_live/new.ex:352 #, elixir-autogen, elixir-format msgid "Longitude" msgstr "Долгота" @@ -1143,7 +1129,7 @@ msgstr "" "подтверждения появляется только после авторизации у соответствующего " "провайдера." -#: lib/who_need_help_web/live/activity_live/show.ex:591 +#: lib/who_need_help_web/live/activity_live/show.ex:598 #, elixir-autogen, elixir-format msgid "Mark completed" msgstr "Отметить завершённой" @@ -1155,12 +1141,12 @@ msgstr "Найдите помощника и согласуйте детали" #: lib/who_need_help_web/live/request_live/index.ex:178 #: lib/who_need_help_web/live/request_live/index.ex:179 -#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:637 #, elixir-autogen, elixir-format msgid "Matched" msgstr "Помощник найден" -#: lib/who_need_help_web/live/request_live/new.ex:113 +#: lib/who_need_help_web/live/request_live/new.ex:118 #, elixir-autogen, elixir-format msgid "Medicine is ready at the pharmacy" msgstr "Лекарство готово к получению в аптеке" @@ -1175,7 +1161,7 @@ msgstr "Заказ лекарств готов в аптеке" msgid "Medicine pickup" msgstr "Доставка лекарств" -#: lib/who_need_help_web/live/request_live/new.ex:156 +#: lib/who_need_help_web/live/request_live/new.ex:161 #, elixir-autogen, elixir-format msgid "Medicine pickup remains the first priority. Roadside categories use the same moderated, extensible request flow with details specific to each type of help." msgstr "" @@ -1196,7 +1182,7 @@ msgstr "" "одобренной группе и уходите или отправляйте жалобу, если ситуация кажется " "небезопасной." -#: lib/who_need_help_web/live/activity_live/show.ex:386 +#: lib/who_need_help_web/live/activity_live/show.ex:390 #, elixir-autogen, elixir-format msgid "Meeting area" msgstr "Место встречи" @@ -1216,12 +1202,12 @@ msgstr "Объединить с" msgid "Merge note" msgstr "Примечание к объединению" -#: lib/who_need_help_web/live/activity_live/show.ex:483 +#: lib/who_need_help_web/live/activity_live/show.ex:487 #, elixir-autogen, elixir-format msgid "Message the approved group" msgstr "Написать одобренной группе" -#: lib/who_need_help_web/live/category_proposal_live.ex:97 +#: lib/who_need_help_web/live/category_proposal_live.ex:103 #, elixir-autogen, elixir-format msgid "Mode" msgstr "Режим" @@ -1255,7 +1241,7 @@ msgstr "ВЗАИМОПОМОЩЬ РЯДОМ" msgid "Nearby help" msgstr "Помощь рядом" -#: lib/who_need_help_web/live/request_live/new.ex:116 +#: lib/who_need_help_web/live/request_live/new.ex:121 #, elixir-autogen, elixir-format msgid "Need roadside help in a safe location" msgstr "Нужна помощь на дороге в безопасном месте" @@ -1265,15 +1251,15 @@ msgstr "Нужна помощь на дороге в безопасном мес msgid "Network" msgstr "Сеть" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:94 #, elixir-autogen, elixir-format msgid "New password" msgstr "Новый пароль" -#: lib/who_need_help_web/live/activity_live/new.ex:207 -#: lib/who_need_help_web/live/activity_live/show.ex:296 -#: lib/who_need_help_web/live/request_live/new.ex:255 -#: lib/who_need_help_web/live/request_live/show.ex:563 +#: lib/who_need_help_web/live/activity_live/new.ex:212 +#: lib/who_need_help_web/live/activity_live/show.ex:300 +#: lib/who_need_help_web/live/request_live/new.ex:260 +#: lib/who_need_help_web/live/request_live/show.ex:568 #, elixir-autogen, elixir-format msgid "No" msgstr "Нет" @@ -1283,8 +1269,8 @@ msgstr "Нет" msgid "No completed help yet." msgstr "Завершённой помощи пока нет." -#: lib/who_need_help_web/live/activity_live/show.ex:451 -#: lib/who_need_help_web/live/request_live/show.ex:771 +#: lib/who_need_help_web/live/activity_live/show.ex:455 +#: lib/who_need_help_web/live/request_live/show.ex:776 #, elixir-autogen, elixir-format msgid "No messages yet." msgstr "Сообщений пока нет." @@ -1304,12 +1290,12 @@ msgstr "Открытых заявок пока нет" msgid "No open signals." msgstr "Открытых сигналов нет." -#: lib/who_need_help_web/live/activity_live/show.ex:586 +#: lib/who_need_help_web/live/activity_live/show.ex:593 #, elixir-autogen, elixir-format msgid "No pending join requests." msgstr "Нет ожидающих заявок на участие." -#: lib/who_need_help_web/live/category_proposal_live.ex:134 +#: lib/who_need_help_web/live/category_proposal_live.ex:140 #, elixir-autogen, elixir-format msgid "No proposals yet." msgstr "Предложений пока нет." @@ -1344,9 +1330,9 @@ msgstr "Не для чрезвычайных ситуаций" #: lib/who_need_help_web/live/request_live/index.ex:168 #: lib/who_need_help_web/live/request_live/index.ex:169 #: lib/who_need_help_web/live/request_live/index.ex:243 -#: lib/who_need_help_web/live/request_live/new.ex:293 -#: lib/who_need_help_web/live/request_live/show.ex:623 -#: lib/who_need_help_web/live/request_live/show.ex:624 +#: lib/who_need_help_web/live/request_live/new.ex:298 +#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/show.ex:629 #, elixir-autogen, elixir-format msgid "Now" msgstr "Сейчас" @@ -1377,7 +1363,7 @@ msgstr "" msgid "Only the conversation linked to this report is displayed." msgstr "Показана только переписка, связанная с этой жалобой." -#: lib/who_need_help_web/live/activity_live/show.ex:437 +#: lib/who_need_help_web/live/activity_live/show.ex:441 #, elixir-autogen, elixir-format msgid "Only the organizer and approved participants can read or send these messages." msgstr "" @@ -1385,11 +1371,11 @@ msgstr "" "сообщения." #: lib/who_need_help_web/live/activity_live/index.ex:166 -#: lib/who_need_help_web/live/activity_live/show.ex:338 +#: lib/who_need_help_web/live/activity_live/show.ex:342 #: lib/who_need_help_web/live/moderation_live.ex:257 #: lib/who_need_help_web/live/request_live/index.ex:176 #: lib/who_need_help_web/live/request_live/index.ex:177 -#: lib/who_need_help_web/live/request_live/show.ex:631 +#: lib/who_need_help_web/live/request_live/show.ex:636 #, elixir-autogen, elixir-format msgid "Open" msgstr "Открыта" @@ -1399,7 +1385,7 @@ msgstr "Открыта" msgid "Open abuse signals" msgstr "Открытые сигналы о злоупотреблениях" -#: lib/who_need_help_web/live/category_proposal_live.ex:128 +#: lib/who_need_help_web/live/category_proposal_live.ex:134 #, elixir-autogen, elixir-format msgid "Open proposals" msgstr "Открытые предложения" @@ -1409,50 +1395,50 @@ msgstr "Открытые предложения" msgid "Optional external thank-you link" msgstr "Необязательная внешняя ссылка для благодарности" -#: lib/who_need_help_web/live/request_live/show.ex:1011 +#: lib/who_need_help_web/live/request_live/show.ex:1024 #, elixir-autogen, elixir-format msgid "Optional thank-you link" msgstr "Необязательная ссылка для благодарности" -#: lib/who_need_help_web/live/activity_live/show.ex:390 +#: lib/who_need_help_web/live/activity_live/show.ex:394 #, elixir-autogen, elixir-format msgid "Organizer" msgstr "Организатор" -#: lib/who_need_help_web/live/activity_live/show.ex:155 +#: lib/who_need_help_web/live/activity_live/show.ex:159 #, elixir-autogen, elixir-format msgid "Organizer blocked. Their activities and messages are hidden." msgstr "Организатор заблокирован. Его активности и сообщения скрыты." -#: lib/who_need_help_web/live/activity_live/show.ex:539 +#: lib/who_need_help_web/live/activity_live/show.ex:546 #, elixir-autogen, elixir-format msgid "Organizer controls" msgstr "Управление организатора" -#: lib/who_need_help_web/live/activity_live/show.ex:399 +#: lib/who_need_help_web/live/activity_live/show.ex:403 #, elixir-autogen, elixir-format msgid "Organizer social links" msgstr "Ссылки организатора на соцсети" -#: lib/who_need_help_web/live/activity_live/show.ex:667 +#: lib/who_need_help_web/live/activity_live/show.ex:679 #: lib/who_need_help_web/live/moderation_live.ex:254 #: lib/who_need_help_web/live/profile_live.ex:311 -#: lib/who_need_help_web/live/request_live/show.ex:1092 +#: lib/who_need_help_web/live/request_live/show.ex:1110 #, elixir-autogen, elixir-format msgid "Other" msgstr "Другое" -#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:112 #, elixir-autogen, elixir-format msgid "Parent (optional)" msgstr "Родительская категория (необязательно)" -#: lib/who_need_help_web/live/activity_live/show.ex:459 +#: lib/who_need_help_web/live/activity_live/show.ex:463 #, elixir-autogen, elixir-format msgid "Participant" msgstr "Участник" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:97 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:130 #, elixir-autogen, elixir-format msgid "Password" msgstr "Пароль" @@ -1462,27 +1448,27 @@ msgstr "Пароль" msgid "Password updated successfully." msgstr "Пароль успешно обновлён." -#: lib/who_need_help_web/live/request_live/new.ex:280 +#: lib/who_need_help_web/live/request_live/new.ex:285 #, elixir-autogen, elixir-format msgid "Pickup instructions (optional)" msgstr "Инструкции для получения (необязательно)" -#: lib/who_need_help_web/live/request_live/show.ex:686 +#: lib/who_need_help_web/live/request_live/show.ex:691 #, elixir-autogen, elixir-format msgid "Pickup notes" msgstr "Инструкции по получению" -#: lib/who_need_help_web/live/activity_live/new.ex:225 +#: lib/who_need_help_web/live/activity_live/new.ex:230 #, elixir-autogen, elixir-format msgid "Plan and expectations" msgstr "План и ожидания" -#: lib/who_need_help_web/live/activity_live/show.ex:421 +#: lib/who_need_help_web/live/activity_live/show.ex:425 #, elixir-autogen, elixir-format msgid "Plan details" msgstr "Подробности плана" -#: lib/who_need_help_web/live/request_live/show.ex:619 +#: lib/who_need_help_web/live/request_live/show.ex:624 #, elixir-autogen, elixir-format msgid "Please check the entered data." msgstr "Пожалуйста, проверьте введенные данные." @@ -1492,12 +1478,12 @@ msgstr "Пожалуйста, проверьте введенные данные msgid "Please check the submitted fields." msgstr "Пожалуйста, проверьте представленные поля." -#: lib/who_need_help_web/live/activity_live/show.ex:334 +#: lib/who_need_help_web/live/activity_live/show.ex:338 #, elixir-autogen, elixir-format msgid "Please check the submitted message." msgstr "Пожалуйста, проверьте отправленное сообщение." -#: lib/who_need_help_web/live/request_live/new.ex:307 +#: lib/who_need_help_web/live/request_live/new.ex:312 #, elixir-autogen, elixir-format msgid "Podil, near the central pharmacy" msgstr "Подол, рядом с центральной аптекой" @@ -1513,7 +1499,7 @@ msgstr "Опубликуйте понятную заявку" msgid "Primary navigation" msgstr "Основная навигация" -#: lib/who_need_help_web/live/request_live/show.ex:755 +#: lib/who_need_help_web/live/request_live/show.ex:760 #, elixir-autogen, elixir-format msgid "Private match chat" msgstr "Приватный чат участников" @@ -1531,7 +1517,7 @@ msgid "Profile updated." msgstr "Профиль обновлён." #: lib/who_need_help_web/live/moderation_live.ex:251 -#: lib/who_need_help_web/live/request_live/show.ex:1089 +#: lib/who_need_help_web/live/request_live/show.ex:1107 #, elixir-autogen, elixir-format msgid "Prohibited item" msgstr "Запрещённый предмет" @@ -1551,14 +1537,14 @@ msgstr "Предложение опубликовано для голосова msgid "Proposal rejected." msgstr "Предложение отклонено." -#: lib/who_need_help_web/live/category_proposal_live.ex:86 +#: lib/who_need_help_web/live/category_proposal_live.ex:87 #, elixir-autogen, elixir-format msgid "Proposals and votes guide moderators. Approval remains a human decision." msgstr "" "Предложения и голоса помогают модераторам, но решение об одобрении всегда " "принимает человек." -#: lib/who_need_help_web/live/category_proposal_live.ex:91 +#: lib/who_need_help_web/live/category_proposal_live.ex:97 #, elixir-autogen, elixir-format msgid "Proposed category" msgstr "Предлагаемая категория" @@ -1575,7 +1561,7 @@ msgstr "" "одним участником другого; производные признаки движения и близости могут " "сохраняться." -#: lib/who_need_help_web/live/activity_live/new.ex:256 +#: lib/who_need_help_web/live/activity_live/new.ex:261 #, elixir-autogen, elixir-format msgid "Public map visibility" msgstr "Видимость на публичной карте" @@ -1590,30 +1576,30 @@ msgstr "URL публичного профиля" msgid "Public reputation" msgstr "Публичная репутация" -#: lib/who_need_help_web/live/activity_live/new.ex:117 +#: lib/who_need_help_web/live/activity_live/new.ex:122 #, elixir-autogen, elixir-format msgid "Publish a clear public plan. Join requests stay pending until you approve them." msgstr "" "Опубликуйте понятный публичный план. Заявки на участие будут ожидать, пока " "вы их не одобрите." -#: lib/who_need_help_web/live/activity_live/new.ex:310 +#: lib/who_need_help_web/live/activity_live/new.ex:315 #, elixir-autogen, elixir-format msgid "Publish activity" msgstr "Опубликовать активность" -#: lib/who_need_help_web/live/category_proposal_live.ex:123 +#: lib/who_need_help_web/live/category_proposal_live.ex:129 #, elixir-autogen, elixir-format msgid "Publish proposal" msgstr "Опубликовать предложение" -#: lib/who_need_help_web/live/request_live/new.ex:364 +#: lib/who_need_help_web/live/request_live/new.ex:369 #, elixir-autogen, elixir-format msgid "Publish request" msgstr "Опубликовать заявку" -#: lib/who_need_help_web/live/activity_live/new.ex:308 -#: lib/who_need_help_web/live/request_live/new.ex:362 +#: lib/who_need_help_web/live/activity_live/new.ex:313 +#: lib/who_need_help_web/live/request_live/new.ex:367 #, elixir-autogen, elixir-format msgid "Publishing…" msgstr "Публикация…" @@ -1643,17 +1629,17 @@ msgstr "" "показатель." #: lib/who_need_help_web/live/leaderboard_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:823 +#: lib/who_need_help_web/live/request_live/show.ex:836 #, elixir-autogen, elixir-format msgid "Rating" msgstr "Оценка" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:40 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 #, elixir-autogen, elixir-format msgid "Read the" msgstr "Прочитайте" -#: lib/who_need_help_web/live/activity_live/show.ex:660 +#: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" msgstr "Причина" @@ -1678,22 +1664,22 @@ msgstr "Примечание об отклонении" msgid "Remove" msgstr "Удалить" -#: lib/who_need_help_web/live/activity_live/show.ex:468 +#: lib/who_need_help_web/live/activity_live/show.ex:472 #, elixir-autogen, elixir-format msgid "Report" msgstr "Пожаловаться" -#: lib/who_need_help_web/live/request_live/show.ex:789 +#: lib/who_need_help_web/live/request_live/show.ex:794 #, elixir-autogen, elixir-format msgid "Report message" msgstr "Пожаловаться на сообщение" -#: lib/who_need_help_web/live/request_live/show.ex:1084 +#: lib/who_need_help_web/live/request_live/show.ex:1102 #, elixir-autogen, elixir-format msgid "Report reason" msgstr "Причина жалобы" -#: lib/who_need_help_web/live/activity_live/show.ex:123 +#: lib/who_need_help_web/live/activity_live/show.ex:126 #: lib/who_need_help_web/live/request_live/show.ex:261 #, elixir-autogen, elixir-format msgid "Report sent to moderators." @@ -1714,7 +1700,7 @@ msgstr "Материалы жалобы" msgid "Reports" msgstr "Жалобы" -#: lib/who_need_help_web/live/request_live/show.ex:1053 +#: lib/who_need_help_web/live/request_live/show.ex:1066 #, elixir-autogen, elixir-format msgid "Reports go to moderators. Blocking hides requests in both directions and prevents new chat messages." msgstr "" @@ -1726,7 +1712,7 @@ msgstr "" msgid "Request cancelled." msgstr "Заявка отменена." -#: lib/who_need_help_web/live/request_live/new.ex:301 +#: lib/who_need_help_web/live/request_live/new.ex:306 #, elixir-autogen, elixir-format msgid "Request expires (UTC)" msgstr "Срок действия заявки (UTC)" @@ -1741,22 +1727,22 @@ msgstr "Заявка скрыта." msgid "Request restored." msgstr "Заявка восстановлена." -#: lib/who_need_help_web/live/activity_live/show.ex:521 +#: lib/who_need_help_web/live/activity_live/show.ex:525 #, elixir-autogen, elixir-format msgid "Request to join" msgstr "Подать заявку на участие" -#: lib/who_need_help_web/live/request_live/show.ex:725 +#: lib/who_need_help_web/live/request_live/show.ex:730 #, elixir-autogen, elixir-format msgid "Requester links:" msgstr "Ссылки заявителя:" -#: lib/who_need_help_web/live/request_live/show.ex:706 +#: lib/who_need_help_web/live/request_live/show.ex:711 #, elixir-autogen, elixir-format msgid "Requester:" msgstr "Заявитель:" -#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:745 #, elixir-autogen, elixir-format msgid "Requester: %{count} completed" msgstr "Автор заявки: %{count} завершено" @@ -1784,6 +1770,7 @@ msgid "Review note" msgstr "Примечание к проверке" #: lib/who_need_help_web/live/request_live/show.ex:233 +#: lib/who_need_help_web/live/request_live/show.ex:825 #, elixir-autogen, elixir-format msgid "Review saved. It appears after both participants review." msgstr "" @@ -1816,8 +1803,8 @@ msgstr "Описание на русском (необязательно)" msgid "Russian name (optional)" msgstr "Название на русском (необязательно)" -#: lib/who_need_help_web/live/activity_live/show.ex:635 -#: lib/who_need_help_web/live/request_live/show.ex:1051 +#: lib/who_need_help_web/live/activity_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:1064 #, elixir-autogen, elixir-format msgid "Safety controls" msgstr "Инструменты безопасности" @@ -1843,7 +1830,7 @@ msgstr "Правила безопасности" msgid "Save" msgstr "Сохранить" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:48 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:108 #, elixir-autogen, elixir-format msgid "Save Password" msgstr "Сохранить пароль" @@ -1856,21 +1843,21 @@ msgstr "Сохранить профиль" #: lib/who_need_help_web/live/request_live/index.ex:172 #: lib/who_need_help_web/live/request_live/index.ex:173 #: lib/who_need_help_web/live/request_live/index.ex:245 -#: lib/who_need_help_web/live/request_live/new.ex:295 -#: lib/who_need_help_web/live/request_live/show.ex:627 -#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/new.ex:300 +#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:633 #, elixir-autogen, elixir-format msgid "Scheduled" msgstr "Запланировано" -#: lib/who_need_help_web/live/activity_live/show.ex:486 -#: lib/who_need_help_web/live/request_live/show.ex:806 +#: lib/who_need_help_web/live/activity_live/show.ex:490 +#: lib/who_need_help_web/live/request_live/show.ex:811 #, elixir-autogen, elixir-format msgid "Send" msgstr "Отправить" -#: lib/who_need_help_web/live/activity_live/show.ex:676 -#: lib/who_need_help_web/live/request_live/show.ex:1101 +#: lib/who_need_help_web/live/activity_live/show.ex:688 +#: lib/who_need_help_web/live/request_live/show.ex:1119 #, elixir-autogen, elixir-format msgid "Send report" msgstr "Отправить жалобу" @@ -1882,12 +1869,12 @@ msgstr "" "Установите CODEX_SESSION_ID в среде развёртывания перед записью " "демонстрации." -#: lib/who_need_help_web/live/request_live/show.ex:1038 +#: lib/who_need_help_web/live/request_live/show.ex:1051 #, elixir-autogen, elixir-format msgid "Share location" msgstr "Поделиться геопозицией" -#: lib/who_need_help_web/live/request_live/new.ex:282 +#: lib/who_need_help_web/live/request_live/new.ex:287 #, elixir-autogen, elixir-format msgid "Share only non-sensitive instructions. Exact details can wait for the matched chat." msgstr "" @@ -1899,8 +1886,8 @@ msgstr "" msgid "Short bio" msgstr "Кратко о себе" -#: lib/who_need_help_web/live/activity_live/new.ex:219 -#: lib/who_need_help_web/live/request_live/new.ex:266 +#: lib/who_need_help_web/live/activity_live/new.ex:224 +#: lib/who_need_help_web/live/request_live/new.ex:271 #, elixir-autogen, elixir-format msgid "Short title" msgstr "Краткий заголовок" @@ -1930,8 +1917,8 @@ msgstr "Слаг" msgid "Social activities" msgstr "Совместные активности" -#: lib/who_need_help_web/live/category_proposal_live.ex:100 -#: lib/who_need_help_web/live/category_proposal_live.ex:143 +#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:149 #, elixir-autogen, elixir-format msgid "Social activity" msgstr "Совместная активность" @@ -1971,24 +1958,24 @@ msgstr "Сеанс подтверждения соцсети завершилс msgid "Social verification session is invalid." msgstr "Сеанс социальной проверки недействителен." -#: lib/who_need_help_web/live/activity_live/show.ex:665 +#: lib/who_need_help_web/live/activity_live/show.ex:677 #: lib/who_need_help_web/live/moderation_live.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:1090 +#: lib/who_need_help_web/live/request_live/show.ex:1108 #, elixir-autogen, elixir-format msgid "Spam" msgstr "Спам" -#: lib/who_need_help_web/live/request_live/show.ex:940 +#: lib/who_need_help_web/live/request_live/show.ex:953 #, elixir-autogen, elixir-format msgid "Start helping" msgstr "Начать помогать" -#: lib/who_need_help_web/live/activity_live/show.ex:374 +#: lib/who_need_help_web/live/activity_live/show.ex:378 #, elixir-autogen, elixir-format msgid "Starts" msgstr "Начало" -#: lib/who_need_help_web/live/activity_live/new.ex:234 +#: lib/who_need_help_web/live/activity_live/new.ex:239 #, elixir-autogen, elixir-format msgid "Starts at (UTC)" msgstr "Начало (UTC)" @@ -1998,7 +1985,7 @@ msgstr "Начало (UTC)" msgid "Status" msgstr "Статус" -#: lib/who_need_help_web/live/request_live/show.ex:1046 +#: lib/who_need_help_web/live/request_live/show.ex:1059 #, elixir-autogen, elixir-format msgid "Stop and delete position" msgstr "Остановить и удалить геопозицию" @@ -2013,7 +2000,7 @@ msgstr "Структурированные поля JSON" msgid "Structured fields must be a valid JSON object." msgstr "Структурированные поля должны быть действительным объектом JSON." -#: lib/who_need_help_web/live/request_live/show.ex:837 +#: lib/who_need_help_web/live/request_live/show.ex:850 #, elixir-autogen, elixir-format msgid "Submit review" msgstr "Отправить отзыв" @@ -2024,13 +2011,13 @@ msgstr "Отправить отзыв" msgid "Suspended" msgstr "Заблокирован" -#: lib/who_need_help_web/live/request_live/show.ex:604 +#: lib/who_need_help_web/live/request_live/show.ex:609 #, elixir-autogen, elixir-format msgid "That handover code is not correct." msgstr "Неверный код передачи." -#: lib/who_need_help_web/live/activity_live/show.ex:140 -#: lib/who_need_help_web/live/request_live/show.ex:278 +#: lib/who_need_help_web/live/activity_live/show.ex:144 +#: lib/who_need_help_web/live/request_live/show.ex:279 #, elixir-autogen, elixir-format msgid "That message cannot be reported from this view." msgstr "На это сообщение нельзя пожаловаться с этого экрана." @@ -2040,7 +2027,7 @@ msgstr "На это сообщение нельзя пожаловаться с msgid "That social account is already linked." msgstr "Эта учётная запись соцсети уже привязана." -#: lib/who_need_help_web/live/request_live/show.ex:608 +#: lib/who_need_help_web/live/request_live/show.ex:613 #, elixir-autogen, elixir-format msgid "That step is not available in the current state." msgstr "Этот шаг недоступен в текущем состоянии." @@ -2055,7 +2042,7 @@ msgstr "Провайдер подтверждения не настроен." msgid "That verification provider is not supported." msgstr "Этот провайдер проверки не поддерживается." -#: lib/who_need_help_web/live/activity_live/show.ex:312 +#: lib/who_need_help_web/live/activity_live/show.ex:316 #, elixir-autogen, elixir-format msgid "The approved group has reached its capacity." msgstr "Одобренная группа уже заполнена." @@ -2075,7 +2062,7 @@ msgstr "Последний администратор не может пониз msgid "The link is invalid or it has expired." msgstr "Ссылка недействительна или просрочена." -#: lib/who_need_help_web/live/activity_live/show.ex:526 +#: lib/who_need_help_web/live/activity_live/show.ex:530 #, elixir-autogen, elixir-format msgid "The organizer has not approved your request yet." msgstr "Организатор ещё не одобрил вашу заявку." @@ -2093,52 +2080,52 @@ msgstr "" "Платформа никогда не обрабатывает, не требует, не разделяет и не гарантирует" " оплату." -#: lib/who_need_help_web/live/activity_live/show.ex:508 +#: lib/who_need_help_web/live/activity_live/show.ex:512 #, elixir-autogen, elixir-format msgid "The public map is approximate or hidden. Exact coordinates appear only after approval." msgstr "" "Публичная карта показывает приблизительную область либо скрыта. Точные " "координаты появляются только после одобрения." -#: lib/who_need_help_web/live/activity_live/show.ex:648 +#: lib/who_need_help_web/live/activity_live/show.ex:660 #, elixir-autogen, elixir-format msgid "The report is scoped to the selected group message." msgstr "Жалоба относится к выбранному сообщению группы." -#: lib/who_need_help_web/live/request_live/show.ex:1076 +#: lib/who_need_help_web/live/request_live/show.ex:1094 #, elixir-autogen, elixir-format msgid "The report will be scoped to the selected chat message." msgstr "Жалоба будет относиться к выбранному сообщению чата." -#: lib/who_need_help_web/live/activity_live/show.ex:315 +#: lib/who_need_help_web/live/activity_live/show.ex:319 #, elixir-autogen, elixir-format msgid "This action is unavailable." msgstr "Это действие недоступно." -#: lib/who_need_help_web/live/activity_live/show.ex:322 +#: lib/who_need_help_web/live/activity_live/show.ex:326 #, elixir-autogen, elixir-format msgid "This activity is no longer open." msgstr "Эта активность больше не открыта." -#: lib/who_need_help_web/live/activity_live/show.ex:320 +#: lib/who_need_help_web/live/activity_live/show.ex:324 #, elixir-autogen, elixir-format msgid "This activity state has already changed." msgstr "Состояние этой активности уже изменилось." -#: lib/who_need_help_web/live/request_live/show.ex:613 +#: lib/who_need_help_web/live/request_live/show.ex:618 #, elixir-autogen, elixir-format msgid "This interaction is blocked." msgstr "Это взаимодействие заблокировано." #: lib/who_need_help_web/live/request_live/index.ex:210 -#: lib/who_need_help_web/live/request_live/show.ex:665 +#: lib/who_need_help_web/live/request_live/show.ex:670 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger." msgstr "" "Это не экстренная служба. При непосредственной опасности обратитесь в " "местные экстренные службы." -#: lib/who_need_help_web/live/request_live/new.ex:164 +#: lib/who_need_help_web/live/request_live/new.ex:169 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger. Never include prescription details, payment card data, access codes, or other sensitive information." msgstr "" @@ -2151,7 +2138,7 @@ msgstr "" msgid "This proposal has already been reviewed." msgstr "Это предложение уже было рассмотрено." -#: lib/who_need_help_web/live/category_proposal_live.ex:56 +#: lib/who_need_help_web/live/category_proposal_live.ex:57 #, elixir-autogen, elixir-format msgid "This proposal is already closed." msgstr "Это предложение уже закрыто." @@ -2161,13 +2148,13 @@ msgstr "Это предложение уже закрыто." msgid "This report has no linked conversation." msgstr "У этой жалобы нет связанной переписки." -#: lib/who_need_help_web/live/request_live/show.ex:1016 +#: lib/who_need_help_web/live/request_live/show.ex:1029 #, elixir-autogen, elixir-format msgid "This request already has a helper." msgstr "У этой заявки уже есть помощник." #: lib/who_need_help_web/live/request_live/show.ex:74 -#: lib/who_need_help_web/live/request_live/show.ex:615 +#: lib/who_need_help_web/live/request_live/show.ex:620 #, elixir-autogen, elixir-format msgid "This request is no longer available." msgstr "Эта заявка больше недоступна." @@ -2185,28 +2172,29 @@ msgstr "Чтобы посмотреть отправленные письма, #: lib/who_need_help_web/live/request_live/index.ex:170 #: lib/who_need_help_web/live/request_live/index.ex:171 #: lib/who_need_help_web/live/request_live/index.ex:244 -#: lib/who_need_help_web/live/request_live/new.ex:294 -#: lib/who_need_help_web/live/request_live/show.ex:625 -#: lib/who_need_help_web/live/request_live/show.ex:626 +#: lib/who_need_help_web/live/request_live/new.ex:299 +#: lib/who_need_help_web/live/request_live/show.ex:630 +#: lib/who_need_help_web/live/request_live/show.ex:631 #, elixir-autogen, elixir-format msgid "Today" msgstr "Сегодня" -#: lib/who_need_help_web/live/activity_live/new.ex:103 -#: lib/who_need_help_web/live/activity_live/show.ex:332 -#: lib/who_need_help_web/live/category_proposal_live.ex:54 +#: lib/who_need_help_web/live/activity_live/new.ex:108 +#: lib/who_need_help_web/live/activity_live/show.ex:336 +#: lib/who_need_help_web/live/category_proposal_live.ex:55 #: lib/who_need_help_web/live/profile_live.ex:59 -#: lib/who_need_help_web/live/request_live/show.ex:618 +#: lib/who_need_help_web/live/request_live/show.ex:623 #, elixir-autogen, elixir-format msgid "Too many actions in the configured time window." msgstr "Слишком много действий за настроенный промежуток времени." +#: lib/who_need_help_web/controllers/google_auth_controller.ex:180 #: lib/who_need_help_web/controllers/user_registration_controller.ex:49 #, elixir-autogen, elixir-format msgid "Too many registration attempts in the configured time window." msgstr "Слишком много попыток регистрации за настроенный промежуток времени." -#: lib/who_need_help_web/live/request_live/new.ex:142 +#: lib/who_need_help_web/live/request_live/new.ex:147 #, elixir-autogen, elixir-format msgid "Too many requests in the configured time window." msgstr "Слишком много запросов за настроенный промежуток времени." @@ -2216,7 +2204,7 @@ msgstr "Слишком много запросов за настроенный msgid "Too many sign-in emails in the configured time window." msgstr "Слишком много писем для входа за настроенный промежуток времени." -#: lib/who_need_help_web/live/category_proposal_live.ex:107 +#: lib/who_need_help_web/live/category_proposal_live.ex:113 #, elixir-autogen, elixir-format msgid "Top level" msgstr "Верхний уровень" @@ -2226,7 +2214,7 @@ msgstr "Верхний уровень" msgid "Total" msgstr "Всего" -#: lib/who_need_help_web/live/activity_live/new.ex:246 +#: lib/who_need_help_web/live/activity_live/new.ex:251 #, elixir-autogen, elixir-format msgid "Total group capacity" msgstr "Общий размер группы" @@ -2246,7 +2234,7 @@ msgstr "Название на украинском (необязательно)" msgid "Unblock" msgstr "Разблокировать" -#: lib/who_need_help_web/live/request_live/show.ex:1069 +#: lib/who_need_help_web/live/request_live/show.ex:1082 #, elixir-autogen, elixir-format msgid "Unblock this user" msgstr "Разблокировать этого пользователя" @@ -2262,18 +2250,18 @@ msgid "Update email instructions" msgstr "Инструкции для изменения электронной почты" #: lib/who_need_help_web/live/request_live/index.ex:240 -#: lib/who_need_help_web/live/request_live/new.ex:291 +#: lib/who_need_help_web/live/request_live/new.ex:296 #, elixir-autogen, elixir-format msgid "Urgency" msgstr "Срочность" -#: lib/who_need_help_web/live/category_proposal_live.ex:99 -#: lib/who_need_help_web/live/category_proposal_live.ex:142 +#: lib/who_need_help_web/live/category_proposal_live.ex:105 +#: lib/who_need_help_web/live/category_proposal_live.ex:148 #, elixir-autogen, elixir-format msgid "Urgent help" msgstr "Срочная помощь" -#: lib/who_need_help_web/live/activity_live/new.ex:123 +#: lib/who_need_help_web/live/activity_live/new.ex:128 #, elixir-autogen, elixir-format msgid "Use a public first meeting point. Do not publish a home address, access code, travel document, or other sensitive information." msgstr "" @@ -2291,8 +2279,8 @@ msgstr "Включить тёмную тему" msgid "Use light theme" msgstr "Включить светлую тему" -#: lib/who_need_help_web/live/activity_live/new.ex:271 -#: lib/who_need_help_web/live/request_live/new.ex:329 +#: lib/who_need_help_web/live/activity_live/new.ex:276 +#: lib/who_need_help_web/live/request_live/new.ex:334 #, elixir-autogen, elixir-format msgid "Use my current foreground location" msgstr "Использовать мою текущую геопозицию" @@ -2316,7 +2304,7 @@ msgstr "Использовать системную тему" msgid "User" msgstr "Пользователь" -#: lib/who_need_help_web/live/request_live/show.ex:307 +#: lib/who_need_help_web/live/request_live/show.ex:308 #, elixir-autogen, elixir-format msgid "User blocked. Their requests and new messages are hidden." msgstr "Пользователь заблокирован. Его заявки и новые сообщения скрыты." @@ -2337,7 +2325,7 @@ msgid "User status updated." msgstr "Обновлен статус пользователя." #: lib/who_need_help_web/live/profile_live.ex:87 -#: lib/who_need_help_web/live/request_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:322 #, elixir-autogen, elixir-format msgid "User unblocked." msgstr "Пользователь разблокирован." @@ -2352,7 +2340,7 @@ msgstr "Подтверждённые люди" msgid "Verify GitHub" msgstr "Подтвердить через GitHub" -#: lib/who_need_help_web/live/request_live/show.ex:996 +#: lib/who_need_help_web/live/request_live/show.ex:1009 #, elixir-autogen, elixir-format msgid "Verify handover" msgstr "Подтвердить передачу" @@ -2372,11 +2360,12 @@ msgstr "Просмотреть связанные доказательства" msgid "Visible reviews" msgstr "Открытые отзывы" -#: lib/who_need_help_web/live/request_live/show.ex:866 +#: lib/who_need_help_web/live/request_live/show.ex:879 #, elixir-autogen, elixir-format msgid "Waiting for a nearby volunteer." msgstr "Ожидаем помощника поблизости." +#: lib/who_need_help_web/controllers/google_auth_controller.ex:137 #: lib/who_need_help_web/controllers/user_session_controller.ex:26 #: lib/who_need_help_web/controllers/user_session_controller.ex:51 #, elixir-autogen, elixir-format @@ -2388,18 +2377,18 @@ msgstr "С возвращением!" msgid "What Codex contributed" msgstr "Что сделал Codex" -#: lib/who_need_help_web/live/activity_live/show.ex:673 -#: lib/who_need_help_web/live/request_live/show.ex:1098 +#: lib/who_need_help_web/live/activity_live/show.ex:685 +#: lib/who_need_help_web/live/request_live/show.ex:1116 #, elixir-autogen, elixir-format msgid "What happened?" msgstr "Что случилось?" -#: lib/who_need_help_web/live/category_proposal_live.ex:83 +#: lib/who_need_help_web/live/category_proposal_live.ex:84 #, elixir-autogen, elixir-format msgid "What help category is missing?" msgstr "Какая категория помощи отсутствует?" -#: lib/who_need_help_web/live/request_live/new.ex:272 +#: lib/who_need_help_web/live/request_live/new.ex:277 #, elixir-autogen, elixir-format msgid "What help do you need?" msgstr "Какая помощь вам нужна?" @@ -2425,7 +2414,7 @@ msgstr "" msgid "Who Need Help home" msgstr "Главная Who Need Help" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:53 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "Who Need Help is not an emergency or medical service. Medicine requests are limited to pickup of legal items already purchased or reserved." msgstr "" @@ -2446,40 +2435,40 @@ msgstr "" msgid "Who needs help?" msgstr "Кому нужна помощь?" -#: lib/who_need_help_web/live/category_proposal_live.ex:121 +#: lib/who_need_help_web/live/category_proposal_live.ex:127 #, elixir-autogen, elixir-format msgid "Why is this useful?" msgstr "Почему это полезно?" -#: lib/who_need_help_web/live/request_live/show.ex:957 +#: lib/who_need_help_web/live/request_live/show.ex:970 #, elixir-autogen, elixir-format msgid "Withdraw from this request" msgstr "Отказаться от этой заявки" -#: lib/who_need_help_web/live/request_live/show.ex:802 +#: lib/who_need_help_web/live/request_live/show.ex:807 #, elixir-autogen, elixir-format msgid "Write a safe coordination message…" msgstr "Напишите безопасное сообщение для координации…" -#: lib/who_need_help_web/live/activity_live/new.ex:204 -#: lib/who_need_help_web/live/activity_live/show.ex:294 -#: lib/who_need_help_web/live/request_live/new.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:561 +#: lib/who_need_help_web/live/activity_live/new.ex:209 +#: lib/who_need_help_web/live/activity_live/show.ex:298 +#: lib/who_need_help_web/live/request_live/new.ex:257 +#: lib/who_need_help_web/live/request_live/show.ex:566 #, elixir-autogen, elixir-format msgid "Yes" msgstr "Да" -#: lib/who_need_help_web/live/activity_live/show.ex:309 +#: lib/who_need_help_web/live/activity_live/show.ex:313 #, elixir-autogen, elixir-format msgid "You already have a join request for this activity." msgstr "Вы уже подали заявку на участие в этой активности." -#: lib/who_need_help_web/live/category_proposal_live.ex:58 +#: lib/who_need_help_web/live/category_proposal_live.ex:59 #, elixir-autogen, elixir-format msgid "You already voted for this proposal." msgstr "Вы уже проголосовали за это предложение." -#: lib/who_need_help_web/live/activity_live/show.ex:506 +#: lib/who_need_help_web/live/activity_live/show.ex:510 #, elixir-autogen, elixir-format msgid "You are approved, so the map can show the exact meeting coordinates." msgstr "" @@ -2490,8 +2479,8 @@ msgstr "" msgid "You are matched. Use chat to coordinate safely." msgstr "Помощник назначен. Безопасно согласуйте детали в чате." -#: lib/who_need_help_web/live/activity_live/show.ex:317 -#: lib/who_need_help_web/live/request_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:610 #, elixir-autogen, elixir-format msgid "You are not allowed to do that." msgstr "У вас нет разрешения на это действие." @@ -2501,7 +2490,7 @@ msgstr "У вас нет разрешения на это действие." msgid "You are running the local mail adapter." msgstr "Вы используете локальный почтовый адаптер." -#: lib/who_need_help_web/live/request_live/new.ex:332 +#: lib/who_need_help_web/live/request_live/new.ex:337 #, elixir-autogen, elixir-format msgid "You can also enter coordinates manually for local testing." msgstr "" @@ -2512,12 +2501,12 @@ msgstr "" msgid "You can be the first person to ask the community." msgstr "Вы можете первыми обратиться за помощью к сообществу." -#: lib/who_need_help_web/live/request_live/show.ex:602 +#: lib/who_need_help_web/live/request_live/show.ex:607 #, elixir-autogen, elixir-format msgid "You cannot accept your own request." msgstr "Нельзя принять собственную заявку." -#: lib/who_need_help_web/live/activity_live/show.ex:316 +#: lib/who_need_help_web/live/activity_live/show.ex:320 #, elixir-autogen, elixir-format msgid "You cannot report your own content." msgstr "Нельзя жаловаться на собственный контент." @@ -2573,7 +2562,7 @@ msgstr "Ваше подтверждение сохранено." msgid "Your profile" msgstr "Ваш профиль" -#: lib/who_need_help_web/live/request_live/new.ex:47 +#: lib/who_need_help_web/live/request_live/new.ex:52 #, elixir-autogen, elixir-format msgid "Your request is now visible to nearby helpers." msgstr "Теперь вашу заявку видят помощники рядом." @@ -2583,7 +2572,7 @@ msgstr "Теперь вашу заявку видят помощники ряд msgid "Your requests" msgstr "Ваши запросы" -#: lib/who_need_help_web/live/request_live/show.ex:817 +#: lib/who_need_help_web/live/request_live/show.ex:822 #, elixir-autogen, elixir-format msgid "Your review is revealed only after both participants submit." msgstr "" @@ -2604,13 +2593,13 @@ msgstr "активность" msgid "activity message" msgstr "сообщение активности" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:42 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "перед подтверждением." #: lib/who_need_help_web/live/activity_live/index.ex:261 -#: lib/who_need_help_web/live/category_proposal_live.ex:147 +#: lib/who_need_help_web/live/category_proposal_live.ex:153 #: lib/who_need_help_web/live/moderation_live.ex:317 #: lib/who_need_help_web/live/request_live/index.ex:286 #, elixir-autogen, elixir-format @@ -2619,7 +2608,7 @@ msgstr "автор: %{name}" #: lib/who_need_help_web/live/profile_live.ex:189 #: lib/who_need_help_web/live/request_live/index.ex:323 -#: lib/who_need_help_web/live/request_live/show.ex:883 +#: lib/who_need_help_web/live/request_live/show.ex:896 #, elixir-autogen, elixir-format msgid "completed" msgstr "завершено" @@ -2634,59 +2623,54 @@ msgstr "истекает %{time}" msgid "for an account now." msgstr "учётную запись сейчас." -#: lib/who_need_help_web/live/request_live/show.ex:910 +#: lib/who_need_help_web/live/request_live/show.ex:923 #, elixir-autogen, elixir-format msgid "helper movement supported" msgstr "движение помощника подтверждено" -#: lib/who_need_help_web/live/request_live/show.ex:911 +#: lib/who_need_help_web/live/request_live/show.ex:924 #, elixir-autogen, elixir-format msgid "no movement evidence" msgstr "нет подтверждения движения" -#: lib/who_need_help_web/live/request_live/show.ex:902 +#: lib/who_need_help_web/live/request_live/show.ex:915 #, elixir-autogen, elixir-format msgid "no proximity evidence" msgstr "нет подтверждения близости" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 -#, elixir-autogen, elixir-format -msgid "or" -msgstr "или" - -#: lib/who_need_help_web/live/activity_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:612 #, elixir-autogen, elixir-format msgid "organizer" msgstr "организатор" -#: lib/who_need_help_web/live/request_live/show.ex:756 +#: lib/who_need_help_web/live/request_live/show.ex:761 #, elixir-autogen, elixir-format msgid "participants only" msgstr "только участники" #: lib/who_need_help_web/live/request_live/index.ex:328 -#: lib/who_need_help_web/live/request_live/show.ex:887 +#: lib/who_need_help_web/live/request_live/show.ex:900 #, elixir-autogen, elixir-format msgid "people" msgstr "люди" -#: lib/who_need_help_web/live/request_live/show.ex:901 +#: lib/who_need_help_web/live/request_live/show.ex:914 #, elixir-autogen, elixir-format msgid "proximity supported" msgstr "близость подтверждена" #: lib/who_need_help_web/live/profile_live.ex:204 -#: lib/who_need_help_web/live/request_live/show.ex:891 +#: lib/who_need_help_web/live/request_live/show.ex:904 #, elixir-autogen, elixir-format msgid "rating" msgstr "оценка" -#: lib/who_need_help_web/live/request_live/show.ex:748 +#: lib/who_need_help_web/live/request_live/show.ex:753 #, elixir-autogen, elixir-format msgid "rating %{rating}" msgstr "оценка %{rating}" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:41 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "Правила безопасности" @@ -2706,12 +2690,12 @@ msgstr "в свою учётную запись сейчас." msgid "unique people" msgstr "Уникальные люди" -#: lib/who_need_help_web/live/activity_live/show.ex:414 -#: lib/who_need_help_web/live/activity_live/show.ex:565 -#: lib/who_need_help_web/live/activity_live/show.ex:624 +#: lib/who_need_help_web/live/activity_live/show.ex:418 +#: lib/who_need_help_web/live/activity_live/show.ex:572 +#: lib/who_need_help_web/live/activity_live/show.ex:631 #: lib/who_need_help_web/live/profile_live.ex:277 -#: lib/who_need_help_web/live/request_live/show.ex:735 -#: lib/who_need_help_web/live/request_live/show.ex:932 +#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:945 #, elixir-autogen, elixir-format msgid "unverified" msgstr "не подтверждено" @@ -2721,13 +2705,13 @@ msgstr "не подтверждено" msgid "urgent" msgstr "срочно" -#: lib/who_need_help_web/live/activity_live/show.ex:413 -#: lib/who_need_help_web/live/activity_live/show.ex:564 -#: lib/who_need_help_web/live/activity_live/show.ex:623 +#: lib/who_need_help_web/live/activity_live/show.ex:417 +#: lib/who_need_help_web/live/activity_live/show.ex:571 +#: lib/who_need_help_web/live/activity_live/show.ex:630 #: lib/who_need_help_web/live/profile_live.ex:276 #: lib/who_need_help_web/live/request_live/index.ex:333 -#: lib/who_need_help_web/live/request_live/show.ex:734 -#: lib/who_need_help_web/live/request_live/show.ex:931 +#: lib/who_need_help_web/live/request_live/show.ex:739 +#: lib/who_need_help_web/live/request_live/show.ex:944 #, elixir-autogen, elixir-format msgid "verified" msgstr "подтверждено" @@ -2737,18 +2721,18 @@ msgstr "подтверждено" msgid "verified handovers" msgstr "подтверждённые передачи" -#: lib/who_need_help_web/live/request_live/show.ex:660 +#: lib/who_need_help_web/live/request_live/show.ex:665 #, elixir-autogen, elixir-format msgid "← All requests" msgstr "← Все заявки" -#: lib/who_need_help_web/live/activity_live/new.ex:113 -#: lib/who_need_help_web/live/activity_live/show.ex:348 +#: lib/who_need_help_web/live/activity_live/new.ex:118 +#: lib/who_need_help_web/live/activity_live/show.ex:352 #, elixir-autogen, elixir-format msgid "← Back to activities" msgstr "← Назад к активностям" -#: lib/who_need_help_web/live/request_live/new.ex:152 +#: lib/who_need_help_web/live/request_live/new.ex:157 #, elixir-autogen, elixir-format msgid "← Back to requests" msgstr "← Назад к заявкам" @@ -2809,15 +2793,15 @@ msgstr "Повторяющаяся пара участников" msgid "Unusual action rate" msgstr "Необычная частота действий" -#: lib/who_need_help_web/live/activity_live/show.ex:448 -#: lib/who_need_help_web/live/request_live/show.ex:768 +#: lib/who_need_help_web/live/activity_live/show.ex:452 +#: lib/who_need_help_web/live/request_live/show.ex:773 #, elixir-autogen, elixir-format msgid "Load earlier messages" msgstr "Загрузить предыдущие сообщения" #: lib/who_need_help_web/live/activity_live/index.ex:273 #: lib/who_need_help_web/live/activity_live/index.ex:310 -#: lib/who_need_help_web/live/category_proposal_live.ex:162 +#: lib/who_need_help_web/live/category_proposal_live.ex:168 #: lib/who_need_help_web/live/leaderboard_live.ex:87 #: lib/who_need_help_web/live/moderation_live.ex:384 #: lib/who_need_help_web/live/moderation_live.ex:419 @@ -2832,9 +2816,9 @@ msgid "Load more" msgstr "Загрузить ещё" #: lib/who_need_help_web/live/activity_live/index.ex:284 -#: lib/who_need_help_web/live/activity_live/show.ex:498 +#: lib/who_need_help_web/live/activity_live/show.ex:502 #: lib/who_need_help_web/live/request_live/index.ex:314 -#: lib/who_need_help_web/live/request_live/show.ex:849 +#: lib/who_need_help_web/live/request_live/show.ex:862 #, elixir-autogen, elixir-format msgid "The map is unavailable in this browser. Request details remain usable." msgstr "Карта недоступна в этом браузере. Остальная информация по-прежнему доступна." @@ -2855,19 +2839,19 @@ msgstr "Подтвердите новый email для своей учётной msgid "Could not complete moderation. Please try again." msgstr "Не удалось завершить модерацию. Попробуйте ещё раз." -#: lib/who_need_help_web/live/activity_live/show.ex:336 -#: lib/who_need_help_web/live/category_proposal_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:621 +#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/category_proposal_live.ex:61 +#: lib/who_need_help_web/live/request_live/show.ex:626 #, elixir-autogen, elixir-format msgid "Could not complete the action. Please try again." msgstr "Не удалось выполнить действие. Попробуйте ещё раз." -#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/activity_live/new.ex:110 #, elixir-autogen, elixir-format msgid "Could not publish the activity. Please try again." msgstr "Не удалось опубликовать активность. Попробуйте ещё раз." -#: lib/who_need_help_web/live/request_live/new.ex:144 +#: lib/who_need_help_web/live/request_live/new.ex:149 #, elixir-autogen, elixir-format msgid "Could not publish the request. Please try again." msgstr "Не удалось опубликовать заявку. Попробуйте ещё раз." @@ -2882,32 +2866,32 @@ msgstr "Не удалось разблокировать пользовател msgid "JavaScript is required to confirm this protected email link." msgstr "Для подтверждения этой защищённой ссылки на email необходим JavaScript." -#: lib/who_need_help_web/live/request_live/show.ex:594 +#: lib/who_need_help_web/live/request_live/show.ex:599 #, elixir-autogen, elixir-format msgid "Last update: %{time}" msgstr "Последнее обновление: %{time}" -#: lib/who_need_help_web/live/request_live/show.ex:617 +#: lib/who_need_help_web/live/request_live/show.ex:622 #, elixir-autogen, elixir-format msgid "Live location sharing is not active." msgstr "Передача геопозиции сейчас не активна." -#: lib/who_need_help_web/live/request_live/show.ex:592 +#: lib/who_need_help_web/live/request_live/show.ex:597 #, elixir-autogen, elixir-format msgid "Shared location" msgstr "Передаваемая геопозиция" -#: lib/who_need_help_web/live/activity_live/show.ex:329 +#: lib/who_need_help_web/live/activity_live/show.ex:333 #, elixir-autogen, elixir-format msgid "The organizer cannot leave the activity." msgstr "Организатор не может покинуть активность." -#: lib/who_need_help_web/live/activity_live/show.ex:326 +#: lib/who_need_help_web/live/activity_live/show.ex:330 #, elixir-autogen, elixir-format msgid "The organizer is already attending." msgstr "Организатор уже участвует." -#: lib/who_need_help_web/controllers/user_registration_controller.ex:60 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:58 #, elixir-autogen, elixir-format msgid "The registration form is invalid." msgstr "Форма регистрации заполнена неверно." @@ -2927,22 +2911,22 @@ msgstr "Форма настроек заполнена неверно." msgid "The sign-in form is invalid." msgstr "Форма входа заполнена неверно." -#: lib/who_need_help_web/live/activity_live/show.ex:323 +#: lib/who_need_help_web/live/activity_live/show.ex:327 #, elixir-autogen, elixir-format msgid "This activity is no longer available." msgstr "Эта активность больше недоступна." -#: lib/who_need_help_web/live/request_live/show.ex:616 +#: lib/who_need_help_web/live/request_live/show.ex:621 #, elixir-autogen, elixir-format msgid "This match is no longer active." msgstr "Это сопоставление больше не активно." -#: lib/who_need_help_web/live/category_proposal_live.ex:57 +#: lib/who_need_help_web/live/category_proposal_live.ex:58 #, elixir-autogen, elixir-format msgid "This proposal is no longer available." msgstr "Это предложение больше недоступно." -#: lib/who_need_help_web/live/request_live/show.ex:614 +#: lib/who_need_help_web/live/request_live/show.ex:619 #, elixir-autogen, elixir-format msgid "This request has expired." msgstr "Срок действия этой заявки истёк." @@ -2957,12 +2941,12 @@ msgstr "Слишком много попыток входа за настрое msgid "Your secure email link is ready. Continue to sign in." msgstr "Защищённая ссылка из письма готова. Продолжите вход." -#: lib/who_need_help_web/controllers/user_settings_controller.ex:128 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:130 #, elixir-autogen, elixir-format msgid "The confirmation email could not be sent. Please try again." msgstr "Не удалось отправить письмо с подтверждением. Попробуйте ещё раз." -#: lib/who_need_help_web/controllers/user_registration_controller.ex:109 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:107 #, elixir-autogen, elixir-format msgid "If this address can be registered or signed in, login instructions will arrive shortly." msgstr "Если этот адрес можно зарегистрировать или использовать для входа, инструкции для входа скоро придут." @@ -2970,9 +2954,191 @@ msgstr "Если этот адрес можно зарегистрировать #: lib/who_need_help_web/live/profile_live.ex:62 #, elixir-autogen, elixir-format msgid "This account cannot perform that action." -msgstr "" +msgstr "Эта учётная запись не может выполнить это действие." #: lib/who_need_help_web/controllers/user_settings_controller.ex:40 #, elixir-autogen, elixir-format msgid "Too many email-change requests in the configured time window." msgstr "Слишком много запросов на смену email за настроенный промежуток времени." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:32 +#, elixir-autogen, elixir-format +msgid "A Google account is connected. You can use it to sign in." +msgstr "Аккаунт Google подключён. Теперь с его помощью можно входить." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:207 +#, elixir-autogen, elixir-format +msgid "A different Google account is already connected." +msgstr "Уже подключён другой аккаунт Google." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:17 +#, elixir-autogen, elixir-format +msgid "A password is not required. We will email a one-time confirmation link. You can add a password later in account settings." +msgstr "Пароль не обязателен. Мы отправим на email одноразовую ссылку для подтверждения. Пароль можно добавить позже в настройках аккаунта." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:170 +#, elixir-autogen, elixir-format +msgid "An account with this email already exists. Sign in by email or password, then connect Google in account settings." +msgstr "Учётная запись с таким email уже существует. Войдите по email или паролю, затем подключите Google в настройках аккаунта." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:37 +#, elixir-autogen, elixir-format +msgid "Confirm that you are 18 or older and accept the safety rules first." +msgstr "Сначала подтвердите, что вам уже исполнилось 18 лет, и примите правила безопасности." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:49 +#, elixir-autogen, elixir-format +msgid "Connect Google account" +msgstr "Подключить аккаунт Google" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Connect Google only after signing in here. We never merge accounts automatically by matching email addresses." +msgstr "Подключайте Google только после входа здесь. Мы никогда не объединяем аккаунты автоматически по совпадению email." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "Create account and email me a link" +msgstr "Создать аккаунт и отправить ссылку на email" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:99 +#, elixir-autogen, elixir-format +msgid "Email me a sign-in link" +msgstr "Отправить ссылку для входа на email" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:215 +#, elixir-autogen, elixir-format +msgid "Google account connection session is invalid." +msgstr "Сессия подключения аккаунта Google недействительна." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:97 +#, elixir-autogen, elixir-format +msgid "Google did not provide a verified email address." +msgstr "Google не предоставил подтверждённый адрес email." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:29 +#, elixir-autogen, elixir-format +msgid "Google sign-in" +msgstr "Вход через Google" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:197 +#, elixir-autogen, elixir-format +msgid "Google sign-in connected." +msgstr "Вход через Google подключён." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:123 +#, elixir-autogen, elixir-format +msgid "Google sign-in could not be started." +msgstr "Не удалось начать вход через Google." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:102 +#, elixir-autogen, elixir-format +msgid "Google sign-in failed. Please try again." +msgstr "Не удалось войти через Google. Попробуйте ещё раз." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:128 +#, elixir-autogen, elixir-format +msgid "Google sign-in is not configured yet." +msgstr "Вход через Google пока не настроен." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:87 +#, elixir-autogen, elixir-format +msgid "Google sign-in session expired. Please start again." +msgstr "Сессия входа через Google истекла. Начните заново." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:91 +#, elixir-autogen, elixir-format +msgid "Google sign-in session is invalid." +msgstr "Сессия входа через Google недействительна." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:47 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Google sign-in will be available after the operator configures it." +msgstr "Вход через Google станет доступен после настройки оператором." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 +#, elixir-autogen, elixir-format +msgid "Sending link..." +msgstr "Отправляем ссылку..." + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:74 +#, elixir-autogen, elixir-format +msgid "Sign in with Google" +msgstr "Войти через Google" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Sign up with Google" +msgstr "Зарегистрироваться через Google" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:202 +#, elixir-autogen, elixir-format +msgid "That Google account belongs to another local account." +msgstr "Этот аккаунт Google принадлежит другой локальной учётной записи." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:45 +#, elixir-autogen, elixir-format +msgid "The Google registration form is invalid." +msgstr "Форма регистрации через Google заполнена неверно." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:260 +#, elixir-autogen, elixir-format +msgid "This Google account cannot be used right now." +msgstr "Этот аккаунт Google сейчас нельзя использовать." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:144 +#, elixir-autogen, elixir-format +msgid "This Google account is not connected yet. Create an account or sign in by email and connect Google in account settings." +msgstr "Этот аккаунт Google ещё не подключён. Создайте учётную запись или войдите по email и подключите Google в настройках аккаунта." + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:109 +#, elixir-autogen, elixir-format +msgid "This works only if you previously added a password in account settings." +msgstr "Это работает, только если вы ранее добавили пароль в настройках аккаунта." + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#, elixir-autogen, elixir-format +msgid "Use a password instead" +msgstr "Войти по паролю" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "We will send a one-time sign-in link. No password is required." +msgstr "Мы отправим одноразовую ссылку для входа. Пароль не требуется." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:163 +#, elixir-autogen, elixir-format +msgid "Your account was created with Google." +msgstr "Ваша учётная запись создана через Google." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:51 +#, elixir-autogen, elixir-format +msgid "or sign up with email" +msgstr "или зарегистрироваться по email" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 +#, elixir-autogen, elixir-format +msgid "or use email" +msgstr "или использовать email" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Disconnect Google" +msgstr "Отключить Google" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Disconnect Google sign-in from this account?" +msgstr "Отключить вход через Google для этой учётной записи?" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:60 +#, elixir-autogen, elixir-format +msgid "Google sign-in disconnected." +msgstr "Вход через Google отключён." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:65 +#, elixir-autogen, elixir-format +msgid "No Google account is connected." +msgstr "Аккаунт Google не подключён." diff --git a/priv/gettext/uk/LC_MESSAGES/default.po b/priv/gettext/uk/LC_MESSAGES/default.po index 4a4d4e6..6609371 100644 --- a/priv/gettext/uk/LC_MESSAGES/default.po +++ b/priv/gettext/uk/LC_MESSAGES/default.po @@ -17,7 +17,7 @@ msgstr "" msgid "Account settings" msgstr "Параметри облікового запису" -#: lib/who_need_help_web/components/core_components.ex:393 +#: lib/who_need_help_web/components/core_components.ex:446 #, elixir-autogen, elixir-format msgid "Actions" msgstr "Дії" @@ -25,7 +25,7 @@ msgstr "Дії" #: lib/who_need_help_web/components/layouts.ex:70 #: lib/who_need_help_web/components/layouts.ex:119 #: lib/who_need_help_web/controllers/page_html/home.html.heex:21 -#: lib/who_need_help_web/live/request_live/new.ex:22 +#: lib/who_need_help_web/live/request_live/new.ex:23 #, elixir-autogen, elixir-format msgid "Ask for help" msgstr "Попросити допомоги" @@ -108,7 +108,7 @@ msgstr "Немає з’єднання з інтернетом" msgid "close" msgstr "закрити" -#: lib/who_need_help_web/live/activity_live/show.ex:363 +#: lib/who_need_help_web/live/activity_live/show.ex:367 #, elixir-autogen, elixir-format msgid "%{approved}/%{capacity} approved" msgstr "Схвалено: %{approved}/%{capacity}" @@ -118,37 +118,37 @@ msgstr "Схвалено: %{approved}/%{capacity}" msgid "%{count} community votes" msgstr "Голосів спільноти: %{count}" -#: lib/who_need_help_web/live/request_live/show.ex:745 +#: lib/who_need_help_web/live/request_live/show.ex:750 #, elixir-autogen, elixir-format msgid "%{count} unique people" msgstr "%{count} унікальних людей" -#: lib/who_need_help_web/live/request_live/show.ex:829 +#: lib/who_need_help_web/live/request_live/show.ex:842 #, elixir-autogen, elixir-format msgid "1 — unsafe" msgstr "1 — небезпечно" -#: lib/who_need_help_web/live/request_live/show.ex:828 +#: lib/who_need_help_web/live/request_live/show.ex:841 #, elixir-autogen, elixir-format msgid "2 — poor" msgstr "2 — погано" -#: lib/who_need_help_web/live/request_live/show.ex:827 +#: lib/who_need_help_web/live/request_live/show.ex:840 #, elixir-autogen, elixir-format msgid "3 — okay" msgstr "3 — нормально" -#: lib/who_need_help_web/live/request_live/show.ex:826 +#: lib/who_need_help_web/live/request_live/show.ex:839 #, elixir-autogen, elixir-format msgid "4 — good" msgstr "4 — добре" -#: lib/who_need_help_web/live/request_live/show.ex:825 +#: lib/who_need_help_web/live/request_live/show.ex:838 #, elixir-autogen, elixir-format msgid "5 — excellent" msgstr "5 — відмінно" -#: lib/who_need_help_web/controllers/user_settings_controller.ex:118 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:120 #, elixir-autogen, elixir-format msgid "A link to confirm your email change has been sent to the new address." msgstr "" @@ -162,13 +162,13 @@ msgstr "" "Сигнал потребує перевірки людиною; відсутність необов’язкового відстеження " "не є доказом зловживання." -#: lib/who_need_help_web/live/request_live/show.ex:859 +#: lib/who_need_help_web/live/request_live/show.ex:872 #, elixir-autogen, elixir-format msgid "Accept only if you can complete this safely and for free." msgstr "" "Приймайте заявку, лише якщо можете безпечно й безкоштовно її виконати." -#: lib/who_need_help_web/live/request_live/show.ex:633 +#: lib/who_need_help_web/live/request_live/show.ex:638 #, elixir-autogen, elixir-format msgid "Accepted" msgstr "Прийнято" @@ -189,7 +189,7 @@ msgstr "Акаунт і мова" msgid "Accounts" msgstr "Акаунти" -#: lib/who_need_help_web/live/request_live/show.ex:855 +#: lib/who_need_help_web/live/request_live/show.ex:868 #, elixir-autogen, elixir-format msgid "Action center" msgstr "Центр дій" @@ -217,7 +217,7 @@ msgstr "" "узгодьте маршрут і заходи безпеки та ніколи не публікуйте домашні адреси, " "коди доступу чи документи для подорожі." -#: lib/who_need_help_web/live/activity_live/new.ex:163 +#: lib/who_need_help_web/live/activity_live/new.ex:168 #, elixir-autogen, elixir-format msgid "Activity details" msgstr "Деталі активності" @@ -228,12 +228,12 @@ msgid "Activity hidden." msgstr "Активність приховано." #: lib/who_need_help_web/live/activity_live/show.ex:18 -#: lib/who_need_help_web/live/activity_live/show.ex:177 +#: lib/who_need_help_web/live/activity_live/show.ex:181 #, elixir-autogen, elixir-format msgid "Activity is unavailable." msgstr "Активність недоступна." -#: lib/who_need_help_web/live/activity_live/new.ex:36 +#: lib/who_need_help_web/live/activity_live/new.ex:41 #, elixir-autogen, elixir-format msgid "Activity published. You approve every participant." msgstr "Активність опубліковано. Ви схвалюєте кожного учасника." @@ -244,7 +244,7 @@ msgid "Activity restored." msgstr "Активність відновлено." #: lib/who_need_help_web/live/activity_live/index.ex:210 -#: lib/who_need_help_web/live/activity_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Activity type" msgstr "Тип активності" @@ -275,7 +275,7 @@ msgstr "Усі категорії" msgid "Already registered?" msgstr "Уже зареєстровані?" -#: lib/who_need_help_web/live/request_live/show.ex:603 +#: lib/who_need_help_web/live/request_live/show.ex:608 #, elixir-autogen, elixir-format msgid "Another helper already accepted this request." msgstr "Інший помічник уже прийняв цю заявку." @@ -285,60 +285,60 @@ msgstr "Інший помічник уже прийняв цю заявку." msgid "Any urgency" msgstr "Будь-яка терміновість" -#: lib/who_need_help_web/live/activity_live/show.ex:524 +#: lib/who_need_help_web/live/activity_live/show.ex:528 #, elixir-autogen, elixir-format msgid "Approval pending" msgstr "Очікує схвалення" -#: lib/who_need_help_web/live/activity_live/show.ex:574 +#: lib/who_need_help_web/live/activity_live/show.ex:581 #, elixir-autogen, elixir-format msgid "Approve" msgstr "Схвалити" -#: lib/who_need_help_web/live/activity_live/show.ex:435 +#: lib/who_need_help_web/live/activity_live/show.ex:439 #, elixir-autogen, elixir-format msgid "Approved group chat" msgstr "Чат схваленої групи" -#: lib/who_need_help_web/live/activity_live/show.ex:600 +#: lib/who_need_help_web/live/activity_live/show.ex:607 #, elixir-autogen, elixir-format msgid "Approved participants" msgstr "Схвалені учасники" #: lib/who_need_help_web/controllers/page_html/home.html.heex:51 -#: lib/who_need_help_web/live/activity_live/new.ex:258 -#: lib/who_need_help_web/live/request_live/new.ex:306 +#: lib/who_need_help_web/live/activity_live/new.ex:263 +#: lib/who_need_help_web/live/request_live/new.ex:311 #, elixir-autogen, elixir-format msgid "Approximate area" msgstr "Орієнтовний район" -#: lib/who_need_help_web/live/activity_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Approximate public meeting area" msgstr "Орієнтовний публічний район зустрічі" #: lib/who_need_help_web/live/profile_live.ex:167 -#: lib/who_need_help_web/live/request_live/show.ex:639 +#: lib/who_need_help_web/live/request_live/show.ex:644 #, elixir-autogen, elixir-format msgid "Approximate publicly" msgstr "Публічно — приблизно" -#: lib/who_need_help_web/live/request_live/new.ex:314 +#: lib/who_need_help_web/live/request_live/new.ex:319 #, elixir-autogen, elixir-format msgid "Approximate publicly (recommended)" msgstr "Показувати приблизно (рекомендовано)" -#: lib/who_need_help_web/live/request_live/show.ex:702 +#: lib/who_need_help_web/live/request_live/show.ex:707 #, elixir-autogen, elixir-format msgid "Area:" msgstr "Район:" -#: lib/who_need_help_web/live/request_live/new.ex:154 +#: lib/who_need_help_web/live/request_live/new.ex:159 #, elixir-autogen, elixir-format msgid "Ask for urgent help" msgstr "Попросити термінову допомогу" -#: lib/who_need_help_web/live/category_proposal_live.ex:92 +#: lib/who_need_help_web/live/category_proposal_live.ex:98 #, elixir-autogen, elixir-format msgid "Bicycle puncture" msgstr "Прокол колеса велосипеда" @@ -358,12 +358,12 @@ msgstr "" msgid "Block and report" msgstr "Блокування та скарга" -#: lib/who_need_help_web/live/activity_live/show.ex:641 +#: lib/who_need_help_web/live/activity_live/show.ex:648 #, elixir-autogen, elixir-format msgid "Block organizer" msgstr "Заблокувати організатора" -#: lib/who_need_help_web/live/request_live/show.ex:1062 +#: lib/who_need_help_web/live/request_live/show.ex:1075 #, elixir-autogen, elixir-format msgid "Block this user" msgstr "Заблокувати користувача" @@ -380,7 +380,7 @@ msgstr "" "Обидва учасники підтверджують завершення, а помічник вводить одноразовий " "код." -#: lib/who_need_help_web/live/request_live/new.ex:118 +#: lib/who_need_help_web/live/request_live/new.ex:123 #, elixir-autogen, elixir-format msgid "Briefly describe the help you need" msgstr "Коротко опишіть, яка допомога вам потрібна" @@ -400,33 +400,33 @@ msgstr "Звіт про розробку" msgid "COMMUNITY TRUST" msgstr "ДОВІРА СПІЛЬНОТИ" -#: lib/who_need_help_web/live/category_proposal_live.ex:80 +#: lib/who_need_help_web/live/category_proposal_live.ex:81 #, elixir-autogen, elixir-format msgid "COMMUNITY-DRIVEN TAXONOMY" msgstr "КАТЕГОРІЇ ВІД СПІЛЬНОТИ" -#: lib/who_need_help_web/live/activity_live/show.ex:594 +#: lib/who_need_help_web/live/activity_live/show.ex:601 #, elixir-autogen, elixir-format msgid "Cancel activity" msgstr "Скасувати активність" -#: lib/who_need_help_web/live/request_live/show.ex:873 -#: lib/who_need_help_web/live/request_live/show.ex:967 +#: lib/who_need_help_web/live/request_live/show.ex:886 +#: lib/who_need_help_web/live/request_live/show.ex:980 #, elixir-autogen, elixir-format msgid "Cancel request" msgstr "Скасувати запит" #: lib/who_need_help_web/live/activity_live/index.ex:168 -#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/activity_live/show.ex:344 #: lib/who_need_help_web/live/request_live/index.ex:184 #: lib/who_need_help_web/live/request_live/index.ex:185 -#: lib/who_need_help_web/live/request_live/show.ex:636 +#: lib/who_need_help_web/live/request_live/show.ex:641 #, elixir-autogen, elixir-format msgid "Cancelled" msgstr "Скасовано" #: lib/who_need_help_web/live/request_live/index.ex:225 -#: lib/who_need_help_web/live/request_live/new.ex:180 +#: lib/who_need_help_web/live/request_live/new.ex:185 #, elixir-autogen, elixir-format msgid "Category" msgstr "Категорія" @@ -436,13 +436,13 @@ msgstr "Категорія" msgid "Category created and proposal approved." msgstr "Категорію створено, а пропозицію схвалено." -#: lib/who_need_help_web/live/request_live/new.ex:211 -#: lib/who_need_help_web/live/request_live/show.ex:691 +#: lib/who_need_help_web/live/request_live/new.ex:216 +#: lib/who_need_help_web/live/request_live/show.ex:696 #, elixir-autogen, elixir-format msgid "Category details" msgstr "Деталі категорії" -#: lib/who_need_help_web/live/category_proposal_live.ex:66 +#: lib/who_need_help_web/live/category_proposal_live.ex:67 #: lib/who_need_help_web/live/moderation_live.ex:466 #, elixir-autogen, elixir-format msgid "Category proposals" @@ -459,7 +459,7 @@ msgid "Change role" msgstr "Змінити роль" #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:21 -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:47 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:107 #, elixir-autogen, elixir-format msgid "Changing..." msgstr "Зміна…" @@ -470,31 +470,31 @@ msgid "Choose a category, urgency, and safe location visibility." msgstr "" "Оберіть категорію, терміновість і безпечний рівень видимості геопозиції." -#: lib/who_need_help_web/live/activity_live/new.ex:140 +#: lib/who_need_help_web/live/activity_live/new.ex:145 #, elixir-autogen, elixir-format msgid "Choose activity" msgstr "Оберіть активність" -#: lib/who_need_help_web/live/request_live/new.ex:181 +#: lib/who_need_help_web/live/request_live/new.ex:186 #, elixir-autogen, elixir-format msgid "Choose category" msgstr "Виберіть категорію" -#: lib/who_need_help_web/live/activity_live/new.ex:175 -#: lib/who_need_help_web/live/activity_live/new.ex:202 -#: lib/who_need_help_web/live/request_live/new.ex:223 -#: lib/who_need_help_web/live/request_live/new.ex:250 +#: lib/who_need_help_web/live/activity_live/new.ex:180 +#: lib/who_need_help_web/live/activity_live/new.ex:207 +#: lib/who_need_help_web/live/request_live/new.ex:228 +#: lib/who_need_help_web/live/request_live/new.ex:255 #, elixir-autogen, elixir-format msgid "Choose…" msgstr "Оберіть…" -#: lib/who_need_help_web/live/activity_live/new.ex:251 +#: lib/who_need_help_web/live/activity_live/new.ex:256 #, elixir-autogen, elixir-format msgid "City centre, near the main entrance" msgstr "Центр міста, біля головного входу" -#: lib/who_need_help_web/live/activity_live/show.ex:654 -#: lib/who_need_help_web/live/request_live/show.ex:1078 +#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/request_live/show.ex:1096 #, elixir-autogen, elixir-format msgid "Clear" msgstr "Очистити" @@ -504,7 +504,7 @@ msgstr "Очистити" msgid "Close" msgstr "Закрити" -#: lib/who_need_help_web/live/activity_live/new.ex:220 +#: lib/who_need_help_web/live/activity_live/new.ex:225 #, elixir-autogen, elixir-format msgid "Coffee and conversation" msgstr "Кава й розмова" @@ -516,7 +516,7 @@ msgstr "" "Кава, кіно, прогулянки та походи відокремлені від термінової допомоги й " "ніколи не впливають на рейтинг помічника." -#: lib/who_need_help_web/live/request_live/show.ex:835 +#: lib/who_need_help_web/live/request_live/show.ex:848 #, elixir-autogen, elixir-format msgid "Comment (optional)" msgstr "Коментар (необов’язково)" @@ -527,19 +527,19 @@ msgid "Community helpers" msgstr "Помічники спільноти" #: lib/who_need_help_web/live/activity_live/index.ex:262 -#: lib/who_need_help_web/live/activity_live/show.ex:392 -#: lib/who_need_help_web/live/activity_live/show.ex:546 -#: lib/who_need_help_web/live/activity_live/show.ex:603 +#: lib/who_need_help_web/live/activity_live/show.ex:396 +#: lib/who_need_help_web/live/activity_live/show.ex:553 +#: lib/who_need_help_web/live/activity_live/show.ex:610 #: lib/who_need_help_web/live/request_live/index.ex:287 #, elixir-autogen, elixir-format msgid "Community member" msgstr "Член спільноти" #: lib/who_need_help_web/live/activity_live/index.ex:167 -#: lib/who_need_help_web/live/activity_live/show.ex:339 +#: lib/who_need_help_web/live/activity_live/show.ex:343 #: lib/who_need_help_web/live/request_live/index.ex:182 #: lib/who_need_help_web/live/request_live/index.ex:183 -#: lib/who_need_help_web/live/request_live/show.ex:635 +#: lib/who_need_help_web/live/request_live/show.ex:640 #, elixir-autogen, elixir-format msgid "Completed" msgstr "Завершено" @@ -554,25 +554,25 @@ msgstr "Підтвердити й увійти лише зараз" msgid "Confirm and stay logged in" msgstr "Підтвердити й залишатися в системі" -#: lib/who_need_help_web/live/request_live/show.ex:947 +#: lib/who_need_help_web/live/request_live/show.ex:960 #, elixir-autogen, elixir-format msgid "Confirm my part is complete" msgstr "Підтвердити виконання своєї частини" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:42 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:102 #, elixir-autogen, elixir-format msgid "Confirm new password" msgstr "Підтвердити новий пароль" -#: lib/who_need_help_web/live/category_proposal_live.ex:51 -#: lib/who_need_help_web/live/request_live/show.ex:611 +#: lib/who_need_help_web/live/category_proposal_live.ex:52 +#: lib/who_need_help_web/live/request_live/show.ex:616 #, elixir-autogen, elixir-format msgid "Confirm your account and ensure it is active first." msgstr "" "Спочатку підтвердьте обліковий запис і переконайтеся, що він активний." -#: lib/who_need_help_web/live/activity_live/new.ex:100 -#: lib/who_need_help_web/live/request_live/new.ex:139 +#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/request_live/new.ex:144 #, elixir-autogen, elixir-format msgid "Confirm your email and ensure your account is active before publishing." msgstr "" @@ -597,17 +597,12 @@ msgid "Create a clear plan in a public place." msgstr "Створіть зрозумілий план у громадському місці." #: lib/who_need_help_web/live/activity_live/index.ex:188 -#: lib/who_need_help_web/live/activity_live/new.ex:11 +#: lib/who_need_help_web/live/activity_live/new.ex:12 #, elixir-autogen, elixir-format msgid "Create activity" msgstr "Створити активність" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:59 -#, elixir-autogen, elixir-format -msgid "Create an account" -msgstr "Створити обліковий запис" - -#: lib/who_need_help_web/live/activity_live/new.ex:115 +#: lib/who_need_help_web/live/activity_live/new.ex:120 #, elixir-autogen, elixir-format msgid "Create an activity" msgstr "Створити активність" @@ -622,18 +617,13 @@ msgstr "Створити категорію" msgid "Create urgent request" msgstr "Створити термінову заявку" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:58 -#, elixir-autogen, elixir-format -msgid "Creating account..." -msgstr "Створення облікового запису…" - -#: lib/who_need_help_web/live/activity_live/show.ex:663 +#: lib/who_need_help_web/live/activity_live/show.ex:675 #, elixir-autogen, elixir-format msgid "Dangerous plan" msgstr "Небезпечний план" #: lib/who_need_help_web/live/moderation_live.ex:248 -#: lib/who_need_help_web/live/request_live/show.ex:1086 +#: lib/who_need_help_web/live/request_live/show.ex:1104 #, elixir-autogen, elixir-format msgid "Dangerous request" msgstr "Небезпечна заявка" @@ -655,7 +645,7 @@ msgstr "" "Рішення та доступ до матеріалів зі скарг на листування записуються до " "журналу аудиту." -#: lib/who_need_help_web/live/activity_live/show.ex:581 +#: lib/who_need_help_web/live/activity_live/show.ex:588 #, elixir-autogen, elixir-format msgid "Decline" msgstr "Відхилити" @@ -665,7 +655,7 @@ msgstr "Відхилити" msgid "Default location visibility" msgstr "Типова видимість геопозиції" -#: lib/who_need_help_web/live/activity_live/new.ex:227 +#: lib/who_need_help_web/live/activity_live/new.ex:232 #, elixir-autogen, elixir-format msgid "Describe the public plan, approximate duration, and who might enjoy it." msgstr "" @@ -678,7 +668,7 @@ msgstr "" msgid "Dismissed" msgstr "Відхилено" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:34 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -697,14 +687,14 @@ msgstr "" msgid "Don't have an account?" msgstr "Немає акаунта?" -#: lib/who_need_help_web/live/request_live/show.ex:815 +#: lib/who_need_help_web/live/request_live/show.ex:820 #, elixir-autogen, elixir-format msgid "Double-blind review" msgstr "Двосторонній прихований відгук" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:25 -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 #: lib/who_need_help_web/controllers/user_session_html/new.html.heex:89 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:122 #: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:15 #, elixir-autogen, elixir-format msgid "Email" @@ -745,7 +735,7 @@ msgstr "Опис англійською" msgid "English name" msgstr "Назва англійською" -#: lib/who_need_help_web/live/request_live/show.ex:992 +#: lib/who_need_help_web/live/request_live/show.ex:1005 #, elixir-autogen, elixir-format msgid "Enter handover code" msgstr "Введіть код передачі" @@ -755,7 +745,7 @@ msgstr "Введіть код передачі" msgid "Exact address after matching" msgstr "Точна адреса після призначення помічника" -#: lib/who_need_help_web/live/activity_live/new.ex:274 +#: lib/who_need_help_web/live/activity_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Exact coordinates are shown only to participants you approve." msgstr "Точні координати показуються лише схваленим вами учасникам." @@ -765,29 +755,29 @@ msgstr "Точні координати показуються лише схва msgid "Exact for active match" msgstr "Точно — для активної заявки" -#: lib/who_need_help_web/live/request_live/new.ex:316 -#: lib/who_need_help_web/live/request_live/show.ex:641 +#: lib/who_need_help_web/live/request_live/new.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:646 #, elixir-autogen, elixir-format msgid "Exact only for active match" msgstr "Точно лише для активної заявки" #: lib/who_need_help_web/live/profile_live.ex:169 -#: lib/who_need_help_web/live/request_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:647 #, elixir-autogen, elixir-format msgid "Exact publicly" msgstr "Публічно — точно" -#: lib/who_need_help_web/live/request_live/new.ex:317 +#: lib/who_need_help_web/live/request_live/new.ex:322 #, elixir-autogen, elixir-format msgid "Exact publicly — explicit consent" msgstr "Публічно — точно, з явною згодою" -#: lib/who_need_help_web/live/request_live/show.ex:710 +#: lib/who_need_help_web/live/request_live/show.ex:715 #, elixir-autogen, elixir-format msgid "Expires:" msgstr "Спливає:" -#: lib/who_need_help_web/live/request_live/new.ex:274 +#: lib/who_need_help_web/live/request_live/new.ex:279 #, elixir-autogen, elixir-format msgid "Explain what is already arranged and what the volunteer needs to do." msgstr "Поясніть, що вже організовано й що має зробити волонтер." @@ -797,9 +787,9 @@ msgstr "Поясніть, що вже організовано й що має з msgid "Find people to join" msgstr "Знайти компанію" -#: lib/who_need_help_web/live/activity_live/show.ex:664 +#: lib/who_need_help_web/live/activity_live/show.ex:676 #: lib/who_need_help_web/live/moderation_live.ex:250 -#: lib/who_need_help_web/live/request_live/show.ex:1088 +#: lib/who_need_help_web/live/request_live/show.ex:1106 #, elixir-autogen, elixir-format msgid "Fraud" msgstr "Шахрайство" @@ -821,7 +811,7 @@ msgstr "" "Підтвердження через GitHub недоступне, доки оператор не налаштує облікові " "дані OAuth. Посилання, введене вручну, не вважається підтвердженим." -#: lib/who_need_help_web/live/request_live/show.ex:975 +#: lib/who_need_help_web/live/request_live/show.ex:988 #, elixir-autogen, elixir-format msgid "HANDOVER CODE FOR THE HELPER" msgstr "КОД ПЕРЕДАЧІ ДЛЯ ПОМІЧНИКА" @@ -836,9 +826,9 @@ msgstr "Ім’я користувача (необов’язково)" msgid "Handover code verified." msgstr "Код передачі підтверджено." -#: lib/who_need_help_web/live/activity_live/show.ex:662 +#: lib/who_need_help_web/live/activity_live/show.ex:674 #: lib/who_need_help_web/live/moderation_live.ex:249 -#: lib/who_need_help_web/live/request_live/show.ex:1087 +#: lib/who_need_help_web/live/request_live/show.ex:1105 #, elixir-autogen, elixir-format msgid "Harassment" msgstr "Домагання" @@ -862,7 +852,7 @@ msgstr "" msgid "Helper" msgstr "Помічник" -#: lib/who_need_help_web/live/request_live/show.ex:878 +#: lib/who_need_help_web/live/request_live/show.ex:891 #, elixir-autogen, elixir-format msgid "Helper: %{name}" msgstr "Помічник: %{name}" @@ -913,10 +903,10 @@ msgstr "" "\n" "Якщо ви не запитували цей лист, просто проігноруйте його." -#: lib/who_need_help_web/live/activity_live/new.ex:259 +#: lib/who_need_help_web/live/activity_live/new.ex:264 #: lib/who_need_help_web/live/profile_live.ex:166 -#: lib/who_need_help_web/live/request_live/new.ex:315 -#: lib/who_need_help_web/live/request_live/show.ex:640 +#: lib/who_need_help_web/live/request_live/new.ex:320 +#: lib/who_need_help_web/live/request_live/show.ex:645 #, elixir-autogen, elixir-format msgid "Hidden" msgstr "Приховано" @@ -942,18 +932,19 @@ msgstr "Сховати запит" msgid "Human decisions retained" msgstr "Рішення залишаються за людиною" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:48 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:39 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:85 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "Мені 18 років, і я приймаю правила безпеки." #: lib/who_need_help_web/controllers/page_html/home.html.heex:56 -#: lib/who_need_help_web/live/request_live/show.ex:862 +#: lib/who_need_help_web/live/request_live/show.ex:875 #, elixir-autogen, elixir-format msgid "I can help" msgstr "Я можу допомогти" -#: lib/who_need_help_web/live/request_live/new.ex:127 +#: lib/who_need_help_web/live/request_live/new.ex:132 #, elixir-autogen, elixir-format msgid "I confirm there is no immediate danger, people and vehicles are away from active traffic or emergency services have already been contacted, and the requested task is lawful." msgstr "" @@ -961,21 +952,21 @@ msgstr "" "перебувають поза активним рухом або екстрені служби вже викликано, а " "запитувана допомога є законною." -#: lib/who_need_help_web/live/request_live/new.ex:121 +#: lib/who_need_help_web/live/request_live/new.ex:126 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, medical advice request, controlled substance, or request to buy medicine on my behalf." msgstr "" "Я підтверджую, що це не екстрена ситуація, не запит медичної консультації чи" " контрольованої речовини й не прохання придбати ліки від мого імені." -#: lib/who_need_help_web/live/request_live/new.ex:133 +#: lib/who_need_help_web/live/request_live/new.ex:138 #, elixir-autogen, elixir-format msgid "I confirm this is not an emergency, there is no immediate danger, and the requested task is lawful." msgstr "" "Я підтверджую, що це не екстрена ситуація, безпосередньої небезпеки немає, а" " запитувана допомога є законною." -#: lib/who_need_help_web/live/activity_live/new.ex:298 +#: lib/who_need_help_web/live/activity_live/new.ex:303 #, elixir-autogen, elixir-format msgid "I will approve participants individually, keep sensitive details in the approved group, and use an appropriate public first meeting point." msgstr "" @@ -994,14 +985,14 @@ msgstr "" "Якщо ця адреса електронної пошти є в нашій системі, незабаром ви отримаєте " "інструкції для входу." -#: lib/who_need_help_web/live/activity_live/show.ex:666 +#: lib/who_need_help_web/live/activity_live/show.ex:678 #: lib/who_need_help_web/live/moderation_live.ex:253 -#: lib/who_need_help_web/live/request_live/show.ex:1091 +#: lib/who_need_help_web/live/request_live/show.ex:1109 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "Видавання себе за іншу особу" -#: lib/who_need_help_web/live/request_live/show.ex:1027 +#: lib/who_need_help_web/live/request_live/show.ex:1040 #, elixir-autogen, elixir-format msgid "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops." msgstr "" @@ -1011,7 +1002,7 @@ msgstr "" #: lib/who_need_help_web/live/request_live/index.ex:180 #: lib/who_need_help_web/live/request_live/index.ex:181 -#: lib/who_need_help_web/live/request_live/show.ex:634 +#: lib/who_need_help_web/live/request_live/show.ex:639 #, elixir-autogen, elixir-format msgid "In progress" msgstr "В процесі" @@ -1031,51 +1022,51 @@ msgstr "Внутрішня нотатка" msgid "Invalid email or password" msgstr "Неправильна електронна пошта або пароль" -#: lib/who_need_help_web/live/activity_live/show.ex:314 +#: lib/who_need_help_web/live/activity_live/show.ex:318 #, elixir-autogen, elixir-format msgid "Join requests are closed." msgstr "Подання заявок на участь закрито." -#: lib/who_need_help_web/live/activity_live/show.ex:380 +#: lib/who_need_help_web/live/activity_live/show.ex:384 #, elixir-autogen, elixir-format msgid "Join requests close" msgstr "Приймання заявок завершується" -#: lib/who_need_help_web/live/activity_live/new.ex:239 +#: lib/who_need_help_web/live/activity_live/new.ex:244 #, elixir-autogen, elixir-format msgid "Join requests close (UTC)" msgstr "Завершення прийому заявок (UTC)" -#: lib/who_need_help_web/live/activity_live/new.ex:282 -#: lib/who_need_help_web/live/request_live/new.ex:340 +#: lib/who_need_help_web/live/activity_live/new.ex:287 +#: lib/who_need_help_web/live/request_live/new.ex:345 #, elixir-autogen, elixir-format msgid "Latitude" msgstr "Широта" -#: lib/who_need_help_web/live/activity_live/show.ex:534 +#: lib/who_need_help_web/live/activity_live/show.ex:541 #, elixir-autogen, elixir-format msgid "Leave activity" msgstr "Залишити активність" -#: lib/who_need_help_web/live/request_live/show.ex:1025 +#: lib/who_need_help_web/live/request_live/show.ex:1038 #, elixir-autogen, elixir-format msgid "Live location" msgstr "Геопозиція в реальному часі" #: lib/who_need_help_web/controllers/page_html/safety.html.heex:51 -#: lib/who_need_help_web/live/activity_live/show.ex:503 +#: lib/who_need_help_web/live/activity_live/show.ex:507 #, elixir-autogen, elixir-format msgid "Location privacy" msgstr "Приватність геопозиції" -#: lib/who_need_help_web/live/request_live/show.ex:914 +#: lib/who_need_help_web/live/request_live/show.ex:927 #, elixir-autogen, elixir-format msgid "Location sharing is optional; missing evidence is not proof of abuse." msgstr "" "Передавання геопозиції необов’язкове; відсутність таких даних не є доказом " "зловживання." -#: lib/who_need_help_web/live/request_live/new.ex:312 +#: lib/who_need_help_web/live/request_live/new.ex:317 #, elixir-autogen, elixir-format msgid "Location visibility" msgstr "Видимість геопозиції" @@ -1085,12 +1076,12 @@ msgstr "Видимість геопозиції" msgid "Location-supported people" msgstr "Люди з підтвердженням геопозицією" -#: lib/who_need_help_web/live/request_live/show.ex:717 +#: lib/who_need_help_web/live/request_live/show.ex:722 #, elixir-autogen, elixir-format msgid "Location:" msgstr "Геопозиція:" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:102 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:136 #, elixir-autogen, elixir-format msgid "Log in and stay logged in" msgstr "Увійти й залишатися в системі" @@ -1100,23 +1091,18 @@ msgstr "Увійти й залишатися в системі" msgid "Log in instructions" msgstr "Інструкції для входу" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:139 #, elixir-autogen, elixir-format msgid "Log in only this time" msgstr "Увійти лише зараз" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 -#, elixir-autogen, elixir-format -msgid "Log in with email" -msgstr "Вхід за допомогою ел. пошти" - #: lib/who_need_help_web/controllers/user_session_controller.ex:117 #, elixir-autogen, elixir-format msgid "Logged out successfully." msgstr "Ви успішно вийшли." -#: lib/who_need_help_web/live/activity_live/new.ex:289 -#: lib/who_need_help_web/live/request_live/new.ex:347 +#: lib/who_need_help_web/live/activity_live/new.ex:294 +#: lib/who_need_help_web/live/request_live/new.ex:352 #, elixir-autogen, elixir-format msgid "Longitude" msgstr "Довгота" @@ -1139,7 +1125,7 @@ msgstr "" "Значок підтвердження з’являється лише після авторизації у відповідного " "провайдера." -#: lib/who_need_help_web/live/activity_live/show.ex:591 +#: lib/who_need_help_web/live/activity_live/show.ex:598 #, elixir-autogen, elixir-format msgid "Mark completed" msgstr "Позначити завершеною" @@ -1151,12 +1137,12 @@ msgstr "Знайдіть помічника й узгодьте деталі" #: lib/who_need_help_web/live/request_live/index.ex:178 #: lib/who_need_help_web/live/request_live/index.ex:179 -#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:637 #, elixir-autogen, elixir-format msgid "Matched" msgstr "Помічника знайдено" -#: lib/who_need_help_web/live/request_live/new.ex:113 +#: lib/who_need_help_web/live/request_live/new.ex:118 #, elixir-autogen, elixir-format msgid "Medicine is ready at the pharmacy" msgstr "Ліки готові до отримання в аптеці" @@ -1171,7 +1157,7 @@ msgstr "Замовлення ліків готове до отримання в msgid "Medicine pickup" msgstr "Доставка ліків" -#: lib/who_need_help_web/live/request_live/new.ex:156 +#: lib/who_need_help_web/live/request_live/new.ex:161 #, elixir-autogen, elixir-format msgid "Medicine pickup remains the first priority. Roadside categories use the same moderated, extensible request flow with details specific to each type of help." msgstr "" @@ -1192,7 +1178,7 @@ msgstr "" "лише схваленій групі й ідіть або надсилайте скаргу, якщо ситуація здається " "небезпечною." -#: lib/who_need_help_web/live/activity_live/show.ex:386 +#: lib/who_need_help_web/live/activity_live/show.ex:390 #, elixir-autogen, elixir-format msgid "Meeting area" msgstr "Область зустрічі" @@ -1212,12 +1198,12 @@ msgstr "Об’єднати з" msgid "Merge note" msgstr "Примітка до об’єднання" -#: lib/who_need_help_web/live/activity_live/show.ex:483 +#: lib/who_need_help_web/live/activity_live/show.ex:487 #, elixir-autogen, elixir-format msgid "Message the approved group" msgstr "Надіслати повідомлення схваленій групі" -#: lib/who_need_help_web/live/category_proposal_live.ex:97 +#: lib/who_need_help_web/live/category_proposal_live.ex:103 #, elixir-autogen, elixir-format msgid "Mode" msgstr "Режим" @@ -1251,7 +1237,7 @@ msgstr "ВЗАЄМОДОПОМОГА ПОРУЧ" msgid "Nearby help" msgstr "Допомога поруч" -#: lib/who_need_help_web/live/request_live/new.ex:116 +#: lib/who_need_help_web/live/request_live/new.ex:121 #, elixir-autogen, elixir-format msgid "Need roadside help in a safe location" msgstr "Потрібна допомога на узбіччі дороги" @@ -1261,15 +1247,15 @@ msgstr "Потрібна допомога на узбіччі дороги" msgid "Network" msgstr "Мережа" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:94 #, elixir-autogen, elixir-format msgid "New password" msgstr "Новий пароль" -#: lib/who_need_help_web/live/activity_live/new.ex:207 -#: lib/who_need_help_web/live/activity_live/show.ex:296 -#: lib/who_need_help_web/live/request_live/new.ex:255 -#: lib/who_need_help_web/live/request_live/show.ex:563 +#: lib/who_need_help_web/live/activity_live/new.ex:212 +#: lib/who_need_help_web/live/activity_live/show.ex:300 +#: lib/who_need_help_web/live/request_live/new.ex:260 +#: lib/who_need_help_web/live/request_live/show.ex:568 #, elixir-autogen, elixir-format msgid "No" msgstr "Ні" @@ -1279,8 +1265,8 @@ msgstr "Ні" msgid "No completed help yet." msgstr "Завершеної допомоги поки немає." -#: lib/who_need_help_web/live/activity_live/show.ex:451 -#: lib/who_need_help_web/live/request_live/show.ex:771 +#: lib/who_need_help_web/live/activity_live/show.ex:455 +#: lib/who_need_help_web/live/request_live/show.ex:776 #, elixir-autogen, elixir-format msgid "No messages yet." msgstr "Ще немає повідомлень." @@ -1300,12 +1286,12 @@ msgstr "Відкритих заявок поки немає" msgid "No open signals." msgstr "Немає відкритих сигналів." -#: lib/who_need_help_web/live/activity_live/show.ex:586 +#: lib/who_need_help_web/live/activity_live/show.ex:593 #, elixir-autogen, elixir-format msgid "No pending join requests." msgstr "Немає заявок на участь, що очікують." -#: lib/who_need_help_web/live/category_proposal_live.ex:134 +#: lib/who_need_help_web/live/category_proposal_live.ex:140 #, elixir-autogen, elixir-format msgid "No proposals yet." msgstr "Пропозицій ще немає." @@ -1340,9 +1326,9 @@ msgstr "Не для екстрених ситуацій" #: lib/who_need_help_web/live/request_live/index.ex:168 #: lib/who_need_help_web/live/request_live/index.ex:169 #: lib/who_need_help_web/live/request_live/index.ex:243 -#: lib/who_need_help_web/live/request_live/new.ex:293 -#: lib/who_need_help_web/live/request_live/show.ex:623 -#: lib/who_need_help_web/live/request_live/show.ex:624 +#: lib/who_need_help_web/live/request_live/new.ex:298 +#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/show.ex:629 #, elixir-autogen, elixir-format msgid "Now" msgstr "Зараз" @@ -1372,7 +1358,7 @@ msgstr "" msgid "Only the conversation linked to this report is displayed." msgstr "Показано лише листування, пов’язане з цією скаргою." -#: lib/who_need_help_web/live/activity_live/show.ex:437 +#: lib/who_need_help_web/live/activity_live/show.ex:441 #, elixir-autogen, elixir-format msgid "Only the organizer and approved participants can read or send these messages." msgstr "" @@ -1380,11 +1366,11 @@ msgstr "" "повідомлення." #: lib/who_need_help_web/live/activity_live/index.ex:166 -#: lib/who_need_help_web/live/activity_live/show.ex:338 +#: lib/who_need_help_web/live/activity_live/show.ex:342 #: lib/who_need_help_web/live/moderation_live.ex:257 #: lib/who_need_help_web/live/request_live/index.ex:176 #: lib/who_need_help_web/live/request_live/index.ex:177 -#: lib/who_need_help_web/live/request_live/show.ex:631 +#: lib/who_need_help_web/live/request_live/show.ex:636 #, elixir-autogen, elixir-format msgid "Open" msgstr "Відкрита" @@ -1394,7 +1380,7 @@ msgstr "Відкрита" msgid "Open abuse signals" msgstr "Відкриті сигнали про зловживання" -#: lib/who_need_help_web/live/category_proposal_live.ex:128 +#: lib/who_need_help_web/live/category_proposal_live.ex:134 #, elixir-autogen, elixir-format msgid "Open proposals" msgstr "Відкриті пропозиції" @@ -1404,50 +1390,50 @@ msgstr "Відкриті пропозиції" msgid "Optional external thank-you link" msgstr "Необов’язкове зовнішнє посилання для подяки" -#: lib/who_need_help_web/live/request_live/show.ex:1011 +#: lib/who_need_help_web/live/request_live/show.ex:1024 #, elixir-autogen, elixir-format msgid "Optional thank-you link" msgstr "Необов’язкове посилання для подяки" -#: lib/who_need_help_web/live/activity_live/show.ex:390 +#: lib/who_need_help_web/live/activity_live/show.ex:394 #, elixir-autogen, elixir-format msgid "Organizer" msgstr "Організатор" -#: lib/who_need_help_web/live/activity_live/show.ex:155 +#: lib/who_need_help_web/live/activity_live/show.ex:159 #, elixir-autogen, elixir-format msgid "Organizer blocked. Their activities and messages are hidden." msgstr "Організатора заблоковано. Його активності й повідомлення приховано." -#: lib/who_need_help_web/live/activity_live/show.ex:539 +#: lib/who_need_help_web/live/activity_live/show.ex:546 #, elixir-autogen, elixir-format msgid "Organizer controls" msgstr "Керування організатора" -#: lib/who_need_help_web/live/activity_live/show.ex:399 +#: lib/who_need_help_web/live/activity_live/show.ex:403 #, elixir-autogen, elixir-format msgid "Organizer social links" msgstr "Посилання організатора на соцмережі" -#: lib/who_need_help_web/live/activity_live/show.ex:667 +#: lib/who_need_help_web/live/activity_live/show.ex:679 #: lib/who_need_help_web/live/moderation_live.ex:254 #: lib/who_need_help_web/live/profile_live.ex:311 -#: lib/who_need_help_web/live/request_live/show.ex:1092 +#: lib/who_need_help_web/live/request_live/show.ex:1110 #, elixir-autogen, elixir-format msgid "Other" msgstr "Інше" -#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:112 #, elixir-autogen, elixir-format msgid "Parent (optional)" msgstr "Батьківська категорія (необов’язково)" -#: lib/who_need_help_web/live/activity_live/show.ex:459 +#: lib/who_need_help_web/live/activity_live/show.ex:463 #, elixir-autogen, elixir-format msgid "Participant" msgstr "Учасник" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:97 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:130 #, elixir-autogen, elixir-format msgid "Password" msgstr "Пароль" @@ -1457,27 +1443,27 @@ msgstr "Пароль" msgid "Password updated successfully." msgstr "Пароль успішно оновлено." -#: lib/who_need_help_web/live/request_live/new.ex:280 +#: lib/who_need_help_web/live/request_live/new.ex:285 #, elixir-autogen, elixir-format msgid "Pickup instructions (optional)" msgstr "Інструкції для отримання (необов’язково)" -#: lib/who_need_help_web/live/request_live/show.ex:686 +#: lib/who_need_help_web/live/request_live/show.ex:691 #, elixir-autogen, elixir-format msgid "Pickup notes" msgstr "Інструкції з отримання" -#: lib/who_need_help_web/live/activity_live/new.ex:225 +#: lib/who_need_help_web/live/activity_live/new.ex:230 #, elixir-autogen, elixir-format msgid "Plan and expectations" msgstr "План і очікування" -#: lib/who_need_help_web/live/activity_live/show.ex:421 +#: lib/who_need_help_web/live/activity_live/show.ex:425 #, elixir-autogen, elixir-format msgid "Plan details" msgstr "Деталі плану" -#: lib/who_need_help_web/live/request_live/show.ex:619 +#: lib/who_need_help_web/live/request_live/show.ex:624 #, elixir-autogen, elixir-format msgid "Please check the entered data." msgstr "Будь ласка, перевірте введені дані." @@ -1487,12 +1473,12 @@ msgstr "Будь ласка, перевірте введені дані." msgid "Please check the submitted fields." msgstr "Будь ласка, перевірте вказані поля." -#: lib/who_need_help_web/live/activity_live/show.ex:334 +#: lib/who_need_help_web/live/activity_live/show.ex:338 #, elixir-autogen, elixir-format msgid "Please check the submitted message." msgstr "Будь ласка, перевірте надіслане повідомлення." -#: lib/who_need_help_web/live/request_live/new.ex:307 +#: lib/who_need_help_web/live/request_live/new.ex:312 #, elixir-autogen, elixir-format msgid "Podil, near the central pharmacy" msgstr "Поділь, поблизу центральної аптеки." @@ -1508,7 +1494,7 @@ msgstr "Опублікуйте зрозумілу заявку" msgid "Primary navigation" msgstr "Основна навігація" -#: lib/who_need_help_web/live/request_live/show.ex:755 +#: lib/who_need_help_web/live/request_live/show.ex:760 #, elixir-autogen, elixir-format msgid "Private match chat" msgstr "Приватний чат учасників" @@ -1526,7 +1512,7 @@ msgid "Profile updated." msgstr "Профіль оновлено." #: lib/who_need_help_web/live/moderation_live.ex:251 -#: lib/who_need_help_web/live/request_live/show.ex:1089 +#: lib/who_need_help_web/live/request_live/show.ex:1107 #, elixir-autogen, elixir-format msgid "Prohibited item" msgstr "Заборонений предмет" @@ -1546,14 +1532,14 @@ msgstr "Пропозицію опубліковано для голосуван msgid "Proposal rejected." msgstr "Пропозицію відхилено." -#: lib/who_need_help_web/live/category_proposal_live.ex:86 +#: lib/who_need_help_web/live/category_proposal_live.ex:87 #, elixir-autogen, elixir-format msgid "Proposals and votes guide moderators. Approval remains a human decision." msgstr "" "Пропозиції та голоси допомагають модераторам, але рішення про схвалення " "завжди ухвалює людина." -#: lib/who_need_help_web/live/category_proposal_live.ex:91 +#: lib/who_need_help_web/live/category_proposal_live.ex:97 #, elixir-autogen, elixir-format msgid "Proposed category" msgstr "Пропонована категорія" @@ -1570,7 +1556,7 @@ msgstr "" " одним учасником іншого; похідні ознаки руху й близькості можуть " "зберігатися." -#: lib/who_need_help_web/live/activity_live/new.ex:256 +#: lib/who_need_help_web/live/activity_live/new.ex:261 #, elixir-autogen, elixir-format msgid "Public map visibility" msgstr "Видимість на публічній мапі" @@ -1585,30 +1571,30 @@ msgstr "URL публічного профілю" msgid "Public reputation" msgstr "Публічна репутація" -#: lib/who_need_help_web/live/activity_live/new.ex:117 +#: lib/who_need_help_web/live/activity_live/new.ex:122 #, elixir-autogen, elixir-format msgid "Publish a clear public plan. Join requests stay pending until you approve them." msgstr "" "Опублікуйте зрозумілий публічний план. Заявки на участь очікуватимуть, доки " "ви їх не схвалите." -#: lib/who_need_help_web/live/activity_live/new.ex:310 +#: lib/who_need_help_web/live/activity_live/new.ex:315 #, elixir-autogen, elixir-format msgid "Publish activity" msgstr "Опублікувати активність" -#: lib/who_need_help_web/live/category_proposal_live.ex:123 +#: lib/who_need_help_web/live/category_proposal_live.ex:129 #, elixir-autogen, elixir-format msgid "Publish proposal" msgstr "Оприлюднити пропозицію" -#: lib/who_need_help_web/live/request_live/new.ex:364 +#: lib/who_need_help_web/live/request_live/new.ex:369 #, elixir-autogen, elixir-format msgid "Publish request" msgstr "Опублікувати заявку" -#: lib/who_need_help_web/live/activity_live/new.ex:308 -#: lib/who_need_help_web/live/request_live/new.ex:362 +#: lib/who_need_help_web/live/activity_live/new.ex:313 +#: lib/who_need_help_web/live/request_live/new.ex:367 #, elixir-autogen, elixir-format msgid "Publishing…" msgstr "Публікація…" @@ -1637,17 +1623,17 @@ msgstr "" "передачі. Повторна допомога між однією парою не збільшує основний показник." #: lib/who_need_help_web/live/leaderboard_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:823 +#: lib/who_need_help_web/live/request_live/show.ex:836 #, elixir-autogen, elixir-format msgid "Rating" msgstr "Оцінка" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:40 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 #, elixir-autogen, elixir-format msgid "Read the" msgstr "Прочитайте" -#: lib/who_need_help_web/live/activity_live/show.ex:660 +#: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" msgstr "Причина" @@ -1672,22 +1658,22 @@ msgstr "Примітка про відхилення" msgid "Remove" msgstr "Вилучити" -#: lib/who_need_help_web/live/activity_live/show.ex:468 +#: lib/who_need_help_web/live/activity_live/show.ex:472 #, elixir-autogen, elixir-format msgid "Report" msgstr "Поскаржитися" -#: lib/who_need_help_web/live/request_live/show.ex:789 +#: lib/who_need_help_web/live/request_live/show.ex:794 #, elixir-autogen, elixir-format msgid "Report message" msgstr "Поскаржитися на повідомлення" -#: lib/who_need_help_web/live/request_live/show.ex:1084 +#: lib/who_need_help_web/live/request_live/show.ex:1102 #, elixir-autogen, elixir-format msgid "Report reason" msgstr "Причина скарги" -#: lib/who_need_help_web/live/activity_live/show.ex:123 +#: lib/who_need_help_web/live/activity_live/show.ex:126 #: lib/who_need_help_web/live/request_live/show.ex:261 #, elixir-autogen, elixir-format msgid "Report sent to moderators." @@ -1708,7 +1694,7 @@ msgstr "Матеріали зі скарги" msgid "Reports" msgstr "Скарги" -#: lib/who_need_help_web/live/request_live/show.ex:1053 +#: lib/who_need_help_web/live/request_live/show.ex:1066 #, elixir-autogen, elixir-format msgid "Reports go to moderators. Blocking hides requests in both directions and prevents new chat messages." msgstr "" @@ -1720,7 +1706,7 @@ msgstr "" msgid "Request cancelled." msgstr "Заявку скасовано." -#: lib/who_need_help_web/live/request_live/new.ex:301 +#: lib/who_need_help_web/live/request_live/new.ex:306 #, elixir-autogen, elixir-format msgid "Request expires (UTC)" msgstr "Запит закінчується (UTC)" @@ -1735,22 +1721,22 @@ msgstr "Заявку приховано." msgid "Request restored." msgstr "Заявку відновлено." -#: lib/who_need_help_web/live/activity_live/show.ex:521 +#: lib/who_need_help_web/live/activity_live/show.ex:525 #, elixir-autogen, elixir-format msgid "Request to join" msgstr "Подати заявку на участь" -#: lib/who_need_help_web/live/request_live/show.ex:725 +#: lib/who_need_help_web/live/request_live/show.ex:730 #, elixir-autogen, elixir-format msgid "Requester links:" msgstr "Посилання заявника:" -#: lib/who_need_help_web/live/request_live/show.ex:706 +#: lib/who_need_help_web/live/request_live/show.ex:711 #, elixir-autogen, elixir-format msgid "Requester:" msgstr "Заявник:" -#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:745 #, elixir-autogen, elixir-format msgid "Requester: %{count} completed" msgstr "Автор заявки: %{count} завершено" @@ -1778,6 +1764,7 @@ msgid "Review note" msgstr "Примітка до перевірки" #: lib/who_need_help_web/live/request_live/show.ex:233 +#: lib/who_need_help_web/live/request_live/show.ex:825 #, elixir-autogen, elixir-format msgid "Review saved. It appears after both participants review." msgstr "" @@ -1811,8 +1798,8 @@ msgstr "Опис російською (необов’язково)" msgid "Russian name (optional)" msgstr "Назва російською (необов’язково)" -#: lib/who_need_help_web/live/activity_live/show.ex:635 -#: lib/who_need_help_web/live/request_live/show.ex:1051 +#: lib/who_need_help_web/live/activity_live/show.ex:642 +#: lib/who_need_help_web/live/request_live/show.ex:1064 #, elixir-autogen, elixir-format msgid "Safety controls" msgstr "Інструменти безпеки" @@ -1838,7 +1825,7 @@ msgstr "Правила безпеки" msgid "Save" msgstr "Зберегти" -#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:48 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:108 #, elixir-autogen, elixir-format msgid "Save Password" msgstr "Зберегти пароль" @@ -1851,21 +1838,21 @@ msgstr "Зберегти профіль" #: lib/who_need_help_web/live/request_live/index.ex:172 #: lib/who_need_help_web/live/request_live/index.ex:173 #: lib/who_need_help_web/live/request_live/index.ex:245 -#: lib/who_need_help_web/live/request_live/new.ex:295 -#: lib/who_need_help_web/live/request_live/show.ex:627 -#: lib/who_need_help_web/live/request_live/show.ex:628 +#: lib/who_need_help_web/live/request_live/new.ex:300 +#: lib/who_need_help_web/live/request_live/show.ex:632 +#: lib/who_need_help_web/live/request_live/show.ex:633 #, elixir-autogen, elixir-format msgid "Scheduled" msgstr "Заплановано" -#: lib/who_need_help_web/live/activity_live/show.ex:486 -#: lib/who_need_help_web/live/request_live/show.ex:806 +#: lib/who_need_help_web/live/activity_live/show.ex:490 +#: lib/who_need_help_web/live/request_live/show.ex:811 #, elixir-autogen, elixir-format msgid "Send" msgstr "Надіслати" -#: lib/who_need_help_web/live/activity_live/show.ex:676 -#: lib/who_need_help_web/live/request_live/show.ex:1101 +#: lib/who_need_help_web/live/activity_live/show.ex:688 +#: lib/who_need_help_web/live/request_live/show.ex:1119 #, elixir-autogen, elixir-format msgid "Send report" msgstr "Надіслати скаргу" @@ -1877,12 +1864,12 @@ msgstr "" "Установіть CODEX_SESSION_ID у середовищі розгортання перед записом " "демонстрації." -#: lib/who_need_help_web/live/request_live/show.ex:1038 +#: lib/who_need_help_web/live/request_live/show.ex:1051 #, elixir-autogen, elixir-format msgid "Share location" msgstr "Поділитися геопозицією" -#: lib/who_need_help_web/live/request_live/new.ex:282 +#: lib/who_need_help_web/live/request_live/new.ex:287 #, elixir-autogen, elixir-format msgid "Share only non-sensitive instructions. Exact details can wait for the matched chat." msgstr "" @@ -1894,8 +1881,8 @@ msgstr "" msgid "Short bio" msgstr "Коротко про себе" -#: lib/who_need_help_web/live/activity_live/new.ex:219 -#: lib/who_need_help_web/live/request_live/new.ex:266 +#: lib/who_need_help_web/live/activity_live/new.ex:224 +#: lib/who_need_help_web/live/request_live/new.ex:271 #, elixir-autogen, elixir-format msgid "Short title" msgstr "Короткий заголовок" @@ -1925,8 +1912,8 @@ msgstr "Слаг" msgid "Social activities" msgstr "Спільні активності" -#: lib/who_need_help_web/live/category_proposal_live.ex:100 -#: lib/who_need_help_web/live/category_proposal_live.ex:143 +#: lib/who_need_help_web/live/category_proposal_live.ex:106 +#: lib/who_need_help_web/live/category_proposal_live.ex:149 #, elixir-autogen, elixir-format msgid "Social activity" msgstr "Спільна активність" @@ -1966,24 +1953,24 @@ msgstr "Сеанс підтвердження соцмережі заверши msgid "Social verification session is invalid." msgstr "Сеанс соціальної перевірки є некоректним." -#: lib/who_need_help_web/live/activity_live/show.ex:665 +#: lib/who_need_help_web/live/activity_live/show.ex:677 #: lib/who_need_help_web/live/moderation_live.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:1090 +#: lib/who_need_help_web/live/request_live/show.ex:1108 #, elixir-autogen, elixir-format msgid "Spam" msgstr "Спам" -#: lib/who_need_help_web/live/request_live/show.ex:940 +#: lib/who_need_help_web/live/request_live/show.ex:953 #, elixir-autogen, elixir-format msgid "Start helping" msgstr "Почати допомагати" -#: lib/who_need_help_web/live/activity_live/show.ex:374 +#: lib/who_need_help_web/live/activity_live/show.ex:378 #, elixir-autogen, elixir-format msgid "Starts" msgstr "Початок" -#: lib/who_need_help_web/live/activity_live/new.ex:234 +#: lib/who_need_help_web/live/activity_live/new.ex:239 #, elixir-autogen, elixir-format msgid "Starts at (UTC)" msgstr "Початок (UTC)" @@ -1993,7 +1980,7 @@ msgstr "Початок (UTC)" msgid "Status" msgstr "Статус" -#: lib/who_need_help_web/live/request_live/show.ex:1046 +#: lib/who_need_help_web/live/request_live/show.ex:1059 #, elixir-autogen, elixir-format msgid "Stop and delete position" msgstr "Зупинити й видалити геопозицію" @@ -2008,7 +1995,7 @@ msgstr "Структуровані поля JSON" msgid "Structured fields must be a valid JSON object." msgstr "Структуровані поля мають бути коректним об’єктом JSON." -#: lib/who_need_help_web/live/request_live/show.ex:837 +#: lib/who_need_help_web/live/request_live/show.ex:850 #, elixir-autogen, elixir-format msgid "Submit review" msgstr "Надіслати відгук" @@ -2019,13 +2006,13 @@ msgstr "Надіслати відгук" msgid "Suspended" msgstr "Заблоковано" -#: lib/who_need_help_web/live/request_live/show.ex:604 +#: lib/who_need_help_web/live/request_live/show.ex:609 #, elixir-autogen, elixir-format msgid "That handover code is not correct." msgstr "Неправильний код передачі." -#: lib/who_need_help_web/live/activity_live/show.ex:140 -#: lib/who_need_help_web/live/request_live/show.ex:278 +#: lib/who_need_help_web/live/activity_live/show.ex:144 +#: lib/who_need_help_web/live/request_live/show.ex:279 #, elixir-autogen, elixir-format msgid "That message cannot be reported from this view." msgstr "На це повідомлення не можна поскаржитися з цього екрана." @@ -2035,7 +2022,7 @@ msgstr "На це повідомлення не можна поскаржити msgid "That social account is already linked." msgstr "Цей обліковий запис соцмережі вже прив’язано." -#: lib/who_need_help_web/live/request_live/show.ex:608 +#: lib/who_need_help_web/live/request_live/show.ex:613 #, elixir-autogen, elixir-format msgid "That step is not available in the current state." msgstr "Цей крок недоступний у поточному стані." @@ -2050,7 +2037,7 @@ msgstr "Провайдер перевірки не налаштовано." msgid "That verification provider is not supported." msgstr "Цей постачальник перевірки не підтримується." -#: lib/who_need_help_web/live/activity_live/show.ex:312 +#: lib/who_need_help_web/live/activity_live/show.ex:316 #, elixir-autogen, elixir-format msgid "The approved group has reached its capacity." msgstr "Схвалена група вже заповнена." @@ -2070,7 +2057,7 @@ msgstr "Останній адміністратор не може понизит msgid "The link is invalid or it has expired." msgstr "Посилання недійсне або прострочене." -#: lib/who_need_help_web/live/activity_live/show.ex:526 +#: lib/who_need_help_web/live/activity_live/show.ex:530 #, elixir-autogen, elixir-format msgid "The organizer has not approved your request yet." msgstr "Організатор ще не схвалив ваш запит." @@ -2088,52 +2075,52 @@ msgstr "" "Платформа ніколи не обробляє, не вимагає, не розподіляє й не гарантує " "оплату." -#: lib/who_need_help_web/live/activity_live/show.ex:508 +#: lib/who_need_help_web/live/activity_live/show.ex:512 #, elixir-autogen, elixir-format msgid "The public map is approximate or hidden. Exact coordinates appear only after approval." msgstr "" "Публічна карта показує приблизну область або прихована. Точні координати " "з’являються лише після схвалення." -#: lib/who_need_help_web/live/activity_live/show.ex:648 +#: lib/who_need_help_web/live/activity_live/show.ex:660 #, elixir-autogen, elixir-format msgid "The report is scoped to the selected group message." msgstr "Скарга стосується вибраного повідомлення групи." -#: lib/who_need_help_web/live/request_live/show.ex:1076 +#: lib/who_need_help_web/live/request_live/show.ex:1094 #, elixir-autogen, elixir-format msgid "The report will be scoped to the selected chat message." msgstr "Скарга стосуватиметься вибраного повідомлення чату." -#: lib/who_need_help_web/live/activity_live/show.ex:315 +#: lib/who_need_help_web/live/activity_live/show.ex:319 #, elixir-autogen, elixir-format msgid "This action is unavailable." msgstr "Ця дія недоступна." -#: lib/who_need_help_web/live/activity_live/show.ex:322 +#: lib/who_need_help_web/live/activity_live/show.ex:326 #, elixir-autogen, elixir-format msgid "This activity is no longer open." msgstr "Ця активність більше не відкрита." -#: lib/who_need_help_web/live/activity_live/show.ex:320 +#: lib/who_need_help_web/live/activity_live/show.ex:324 #, elixir-autogen, elixir-format msgid "This activity state has already changed." msgstr "Цей стан активності вже змінено." -#: lib/who_need_help_web/live/request_live/show.ex:613 +#: lib/who_need_help_web/live/request_live/show.ex:618 #, elixir-autogen, elixir-format msgid "This interaction is blocked." msgstr "Ця взаємодія заблокована." #: lib/who_need_help_web/live/request_live/index.ex:210 -#: lib/who_need_help_web/live/request_live/show.ex:665 +#: lib/who_need_help_web/live/request_live/show.ex:670 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger." msgstr "" "Це не екстрена служба. За безпосередньої небезпеки зверніться до місцевих " "екстрених служб." -#: lib/who_need_help_web/live/request_live/new.ex:164 +#: lib/who_need_help_web/live/request_live/new.ex:169 #, elixir-autogen, elixir-format msgid "This is not an emergency service. Contact local emergency services for immediate danger. Never include prescription details, payment card data, access codes, or other sensitive information." msgstr "" @@ -2146,7 +2133,7 @@ msgstr "" msgid "This proposal has already been reviewed." msgstr "Цю пропозицію вже розглянуто." -#: lib/who_need_help_web/live/category_proposal_live.ex:56 +#: lib/who_need_help_web/live/category_proposal_live.ex:57 #, elixir-autogen, elixir-format msgid "This proposal is already closed." msgstr "Ця пропозиція вже закрита." @@ -2156,13 +2143,13 @@ msgstr "Ця пропозиція вже закрита." msgid "This report has no linked conversation." msgstr "Ця скарга не має пов’язаного листування." -#: lib/who_need_help_web/live/request_live/show.ex:1016 +#: lib/who_need_help_web/live/request_live/show.ex:1029 #, elixir-autogen, elixir-format msgid "This request already has a helper." msgstr "Ця заявка вже має помічника." #: lib/who_need_help_web/live/request_live/show.ex:74 -#: lib/who_need_help_web/live/request_live/show.ex:615 +#: lib/who_need_help_web/live/request_live/show.ex:620 #, elixir-autogen, elixir-format msgid "This request is no longer available." msgstr "Ця заявка більше не доступна." @@ -2180,28 +2167,29 @@ msgstr "Щоб переглянути надіслані листи, відкр #: lib/who_need_help_web/live/request_live/index.ex:170 #: lib/who_need_help_web/live/request_live/index.ex:171 #: lib/who_need_help_web/live/request_live/index.ex:244 -#: lib/who_need_help_web/live/request_live/new.ex:294 -#: lib/who_need_help_web/live/request_live/show.ex:625 -#: lib/who_need_help_web/live/request_live/show.ex:626 +#: lib/who_need_help_web/live/request_live/new.ex:299 +#: lib/who_need_help_web/live/request_live/show.ex:630 +#: lib/who_need_help_web/live/request_live/show.ex:631 #, elixir-autogen, elixir-format msgid "Today" msgstr "Сьогодні" -#: lib/who_need_help_web/live/activity_live/new.ex:103 -#: lib/who_need_help_web/live/activity_live/show.ex:332 -#: lib/who_need_help_web/live/category_proposal_live.ex:54 +#: lib/who_need_help_web/live/activity_live/new.ex:108 +#: lib/who_need_help_web/live/activity_live/show.ex:336 +#: lib/who_need_help_web/live/category_proposal_live.ex:55 #: lib/who_need_help_web/live/profile_live.ex:59 -#: lib/who_need_help_web/live/request_live/show.ex:618 +#: lib/who_need_help_web/live/request_live/show.ex:623 #, elixir-autogen, elixir-format msgid "Too many actions in the configured time window." msgstr "Забагато дій протягом налаштованого проміжку часу." +#: lib/who_need_help_web/controllers/google_auth_controller.ex:180 #: lib/who_need_help_web/controllers/user_registration_controller.ex:49 #, elixir-autogen, elixir-format msgid "Too many registration attempts in the configured time window." msgstr "Забагато спроб реєстрації протягом налаштованого проміжку часу." -#: lib/who_need_help_web/live/request_live/new.ex:142 +#: lib/who_need_help_web/live/request_live/new.ex:147 #, elixir-autogen, elixir-format msgid "Too many requests in the configured time window." msgstr "Забагато запитів протягом налаштованого проміжку часу." @@ -2211,7 +2199,7 @@ msgstr "Забагато запитів протягом налаштовано msgid "Too many sign-in emails in the configured time window." msgstr "Забагато листів для входу протягом налаштованого проміжку часу." -#: lib/who_need_help_web/live/category_proposal_live.ex:107 +#: lib/who_need_help_web/live/category_proposal_live.ex:113 #, elixir-autogen, elixir-format msgid "Top level" msgstr "Верхній рівень" @@ -2221,7 +2209,7 @@ msgstr "Верхній рівень" msgid "Total" msgstr "Загалом" -#: lib/who_need_help_web/live/activity_live/new.ex:246 +#: lib/who_need_help_web/live/activity_live/new.ex:251 #, elixir-autogen, elixir-format msgid "Total group capacity" msgstr "Загальний розмір групи" @@ -2241,7 +2229,7 @@ msgstr "Назва українською (необов’язково)" msgid "Unblock" msgstr "Розблокувати" -#: lib/who_need_help_web/live/request_live/show.ex:1069 +#: lib/who_need_help_web/live/request_live/show.ex:1082 #, elixir-autogen, elixir-format msgid "Unblock this user" msgstr "Розблокувати цього користувача" @@ -2257,18 +2245,18 @@ msgid "Update email instructions" msgstr "Інструкції для зміни електронної пошти" #: lib/who_need_help_web/live/request_live/index.ex:240 -#: lib/who_need_help_web/live/request_live/new.ex:291 +#: lib/who_need_help_web/live/request_live/new.ex:296 #, elixir-autogen, elixir-format msgid "Urgency" msgstr "Терміновість" -#: lib/who_need_help_web/live/category_proposal_live.ex:99 -#: lib/who_need_help_web/live/category_proposal_live.ex:142 +#: lib/who_need_help_web/live/category_proposal_live.ex:105 +#: lib/who_need_help_web/live/category_proposal_live.ex:148 #, elixir-autogen, elixir-format msgid "Urgent help" msgstr "Термінова допомога" -#: lib/who_need_help_web/live/activity_live/new.ex:123 +#: lib/who_need_help_web/live/activity_live/new.ex:128 #, elixir-autogen, elixir-format msgid "Use a public first meeting point. Do not publish a home address, access code, travel document, or other sensitive information." msgstr "" @@ -2285,8 +2273,8 @@ msgstr "Увімкнути темну тему" msgid "Use light theme" msgstr "Увімкнути світлу тему" -#: lib/who_need_help_web/live/activity_live/new.ex:271 -#: lib/who_need_help_web/live/request_live/new.ex:329 +#: lib/who_need_help_web/live/activity_live/new.ex:276 +#: lib/who_need_help_web/live/request_live/new.ex:334 #, elixir-autogen, elixir-format msgid "Use my current foreground location" msgstr "Використати мою поточну геопозицію" @@ -2310,7 +2298,7 @@ msgstr "Використати системну тему" msgid "User" msgstr "Користувач" -#: lib/who_need_help_web/live/request_live/show.ex:307 +#: lib/who_need_help_web/live/request_live/show.ex:308 #, elixir-autogen, elixir-format msgid "User blocked. Their requests and new messages are hidden." msgstr "Користувача заблоковано. Його заявки й нові повідомлення приховано." @@ -2331,7 +2319,7 @@ msgid "User status updated." msgstr "Статус користувача оновлено." #: lib/who_need_help_web/live/profile_live.ex:87 -#: lib/who_need_help_web/live/request_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:322 #, elixir-autogen, elixir-format msgid "User unblocked." msgstr "Користувача розблоковано." @@ -2346,7 +2334,7 @@ msgstr "Підтверджені люди" msgid "Verify GitHub" msgstr "Підтвердити через GitHub" -#: lib/who_need_help_web/live/request_live/show.ex:996 +#: lib/who_need_help_web/live/request_live/show.ex:1009 #, elixir-autogen, elixir-format msgid "Verify handover" msgstr "Підтвердити передачу" @@ -2366,11 +2354,12 @@ msgstr "Переглянути пов’язані докази" msgid "Visible reviews" msgstr "Відкриті відгуки" -#: lib/who_need_help_web/live/request_live/show.ex:866 +#: lib/who_need_help_web/live/request_live/show.ex:879 #, elixir-autogen, elixir-format msgid "Waiting for a nearby volunteer." msgstr "Очікуємо помічника поруч." +#: lib/who_need_help_web/controllers/google_auth_controller.ex:137 #: lib/who_need_help_web/controllers/user_session_controller.ex:26 #: lib/who_need_help_web/controllers/user_session_controller.ex:51 #, elixir-autogen, elixir-format @@ -2382,18 +2371,18 @@ msgstr "З поверненням!" msgid "What Codex contributed" msgstr "Що зробив Codex" -#: lib/who_need_help_web/live/activity_live/show.ex:673 -#: lib/who_need_help_web/live/request_live/show.ex:1098 +#: lib/who_need_help_web/live/activity_live/show.ex:685 +#: lib/who_need_help_web/live/request_live/show.ex:1116 #, elixir-autogen, elixir-format msgid "What happened?" msgstr "Що сталося?" -#: lib/who_need_help_web/live/category_proposal_live.ex:83 +#: lib/who_need_help_web/live/category_proposal_live.ex:84 #, elixir-autogen, elixir-format msgid "What help category is missing?" msgstr "Яка категорія допомоги відсутня?" -#: lib/who_need_help_web/live/request_live/new.ex:272 +#: lib/who_need_help_web/live/request_live/new.ex:277 #, elixir-autogen, elixir-format msgid "What help do you need?" msgstr "Яка допомога вам потрібна?" @@ -2419,7 +2408,7 @@ msgstr "" msgid "Who Need Help home" msgstr "Головна Who Need Help" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:53 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "Who Need Help is not an emergency or medical service. Medicine requests are limited to pickup of legal items already purchased or reserved." msgstr "" @@ -2439,40 +2428,40 @@ msgstr "" msgid "Who needs help?" msgstr "Хто потребує допомоги?" -#: lib/who_need_help_web/live/category_proposal_live.ex:121 +#: lib/who_need_help_web/live/category_proposal_live.ex:127 #, elixir-autogen, elixir-format msgid "Why is this useful?" msgstr "Чому це корисно?" -#: lib/who_need_help_web/live/request_live/show.ex:957 +#: lib/who_need_help_web/live/request_live/show.ex:970 #, elixir-autogen, elixir-format msgid "Withdraw from this request" msgstr "Відмовитися від цієї заявки" -#: lib/who_need_help_web/live/request_live/show.ex:802 +#: lib/who_need_help_web/live/request_live/show.ex:807 #, elixir-autogen, elixir-format msgid "Write a safe coordination message…" msgstr "Напишіть безпечне повідомлення для координації…" -#: lib/who_need_help_web/live/activity_live/new.ex:204 -#: lib/who_need_help_web/live/activity_live/show.ex:294 -#: lib/who_need_help_web/live/request_live/new.ex:252 -#: lib/who_need_help_web/live/request_live/show.ex:561 +#: lib/who_need_help_web/live/activity_live/new.ex:209 +#: lib/who_need_help_web/live/activity_live/show.ex:298 +#: lib/who_need_help_web/live/request_live/new.ex:257 +#: lib/who_need_help_web/live/request_live/show.ex:566 #, elixir-autogen, elixir-format msgid "Yes" msgstr "Так" -#: lib/who_need_help_web/live/activity_live/show.ex:309 +#: lib/who_need_help_web/live/activity_live/show.ex:313 #, elixir-autogen, elixir-format msgid "You already have a join request for this activity." msgstr "Ви вже подали заявку на участь у цій активності." -#: lib/who_need_help_web/live/category_proposal_live.ex:58 +#: lib/who_need_help_web/live/category_proposal_live.ex:59 #, elixir-autogen, elixir-format msgid "You already voted for this proposal." msgstr "Ви вже проголосували за цю пропозицію." -#: lib/who_need_help_web/live/activity_live/show.ex:506 +#: lib/who_need_help_web/live/activity_live/show.ex:510 #, elixir-autogen, elixir-format msgid "You are approved, so the map can show the exact meeting coordinates." msgstr "Вас схвалено, тому карта може показувати точні координати зустрічі." @@ -2482,8 +2471,8 @@ msgstr "Вас схвалено, тому карта може показуват msgid "You are matched. Use chat to coordinate safely." msgstr "Помічника призначено. Безпечно узгодьте деталі в чаті." -#: lib/who_need_help_web/live/activity_live/show.ex:317 -#: lib/who_need_help_web/live/request_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:321 +#: lib/who_need_help_web/live/request_live/show.ex:610 #, elixir-autogen, elixir-format msgid "You are not allowed to do that." msgstr "Ви не маєте дозволу на цю дію." @@ -2493,7 +2482,7 @@ msgstr "Ви не маєте дозволу на цю дію." msgid "You are running the local mail adapter." msgstr "Використовується локальний поштовий адаптер." -#: lib/who_need_help_web/live/request_live/new.ex:332 +#: lib/who_need_help_web/live/request_live/new.ex:337 #, elixir-autogen, elixir-format msgid "You can also enter coordinates manually for local testing." msgstr "Ви також можете ввести координати вручну для локального тестування." @@ -2503,12 +2492,12 @@ msgstr "Ви також можете ввести координати вруч msgid "You can be the first person to ask the community." msgstr "Ви можете першими звернутися по допомогу до спільноти." -#: lib/who_need_help_web/live/request_live/show.ex:602 +#: lib/who_need_help_web/live/request_live/show.ex:607 #, elixir-autogen, elixir-format msgid "You cannot accept your own request." msgstr "Не можна прийняти власну заявку." -#: lib/who_need_help_web/live/activity_live/show.ex:316 +#: lib/who_need_help_web/live/activity_live/show.ex:320 #, elixir-autogen, elixir-format msgid "You cannot report your own content." msgstr "Не можна скаржитися на власний вміст." @@ -2563,7 +2552,7 @@ msgstr "Ваше підтвердження збережено." msgid "Your profile" msgstr "Ваш профіль" -#: lib/who_need_help_web/live/request_live/new.ex:47 +#: lib/who_need_help_web/live/request_live/new.ex:52 #, elixir-autogen, elixir-format msgid "Your request is now visible to nearby helpers." msgstr "Тепер вашу заявку бачать помічники поруч." @@ -2573,7 +2562,7 @@ msgstr "Тепер вашу заявку бачать помічники пор msgid "Your requests" msgstr "Ваші запити" -#: lib/who_need_help_web/live/request_live/show.ex:817 +#: lib/who_need_help_web/live/request_live/show.ex:822 #, elixir-autogen, elixir-format msgid "Your review is revealed only after both participants submit." msgstr "" @@ -2594,13 +2583,13 @@ msgstr "активність" msgid "activity message" msgstr "повідомлення активності" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:42 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "перед підтвердженням." #: lib/who_need_help_web/live/activity_live/index.ex:261 -#: lib/who_need_help_web/live/category_proposal_live.ex:147 +#: lib/who_need_help_web/live/category_proposal_live.ex:153 #: lib/who_need_help_web/live/moderation_live.ex:317 #: lib/who_need_help_web/live/request_live/index.ex:286 #, elixir-autogen, elixir-format @@ -2609,7 +2598,7 @@ msgstr "автор: %{name}" #: lib/who_need_help_web/live/profile_live.ex:189 #: lib/who_need_help_web/live/request_live/index.ex:323 -#: lib/who_need_help_web/live/request_live/show.ex:883 +#: lib/who_need_help_web/live/request_live/show.ex:896 #, elixir-autogen, elixir-format msgid "completed" msgstr "завершено" @@ -2624,59 +2613,54 @@ msgstr "спливає %{time}" msgid "for an account now." msgstr "обліковий запис зараз." -#: lib/who_need_help_web/live/request_live/show.ex:910 +#: lib/who_need_help_web/live/request_live/show.ex:923 #, elixir-autogen, elixir-format msgid "helper movement supported" msgstr "рух помічника підтверджено" -#: lib/who_need_help_web/live/request_live/show.ex:911 +#: lib/who_need_help_web/live/request_live/show.ex:924 #, elixir-autogen, elixir-format msgid "no movement evidence" msgstr "немає підтвердження руху" -#: lib/who_need_help_web/live/request_live/show.ex:902 +#: lib/who_need_help_web/live/request_live/show.ex:915 #, elixir-autogen, elixir-format msgid "no proximity evidence" msgstr "немає підтвердження близькості" -#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 -#, elixir-autogen, elixir-format -msgid "or" -msgstr "або" - -#: lib/who_need_help_web/live/activity_live/show.ex:605 +#: lib/who_need_help_web/live/activity_live/show.ex:612 #, elixir-autogen, elixir-format msgid "organizer" msgstr "організатор" -#: lib/who_need_help_web/live/request_live/show.ex:756 +#: lib/who_need_help_web/live/request_live/show.ex:761 #, elixir-autogen, elixir-format msgid "participants only" msgstr "лише учасники" #: lib/who_need_help_web/live/request_live/index.ex:328 -#: lib/who_need_help_web/live/request_live/show.ex:887 +#: lib/who_need_help_web/live/request_live/show.ex:900 #, elixir-autogen, elixir-format msgid "people" msgstr "люди" -#: lib/who_need_help_web/live/request_live/show.ex:901 +#: lib/who_need_help_web/live/request_live/show.ex:914 #, elixir-autogen, elixir-format msgid "proximity supported" msgstr "близькість підтверджено" #: lib/who_need_help_web/live/profile_live.ex:204 -#: lib/who_need_help_web/live/request_live/show.ex:891 +#: lib/who_need_help_web/live/request_live/show.ex:904 #, elixir-autogen, elixir-format msgid "rating" msgstr "оцінка" -#: lib/who_need_help_web/live/request_live/show.ex:748 +#: lib/who_need_help_web/live/request_live/show.ex:753 #, elixir-autogen, elixir-format msgid "rating %{rating}" msgstr "оцінка %{rating}" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:41 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "правила безпеки" @@ -2696,12 +2680,12 @@ msgstr "до свого облікового запису зараз." msgid "unique people" msgstr "унікальні люди" -#: lib/who_need_help_web/live/activity_live/show.ex:414 -#: lib/who_need_help_web/live/activity_live/show.ex:565 -#: lib/who_need_help_web/live/activity_live/show.ex:624 +#: lib/who_need_help_web/live/activity_live/show.ex:418 +#: lib/who_need_help_web/live/activity_live/show.ex:572 +#: lib/who_need_help_web/live/activity_live/show.ex:631 #: lib/who_need_help_web/live/profile_live.ex:277 -#: lib/who_need_help_web/live/request_live/show.ex:735 -#: lib/who_need_help_web/live/request_live/show.ex:932 +#: lib/who_need_help_web/live/request_live/show.ex:740 +#: lib/who_need_help_web/live/request_live/show.ex:945 #, elixir-autogen, elixir-format msgid "unverified" msgstr "не підтверджено" @@ -2711,13 +2695,13 @@ msgstr "не підтверджено" msgid "urgent" msgstr "терміново" -#: lib/who_need_help_web/live/activity_live/show.ex:413 -#: lib/who_need_help_web/live/activity_live/show.ex:564 -#: lib/who_need_help_web/live/activity_live/show.ex:623 +#: lib/who_need_help_web/live/activity_live/show.ex:417 +#: lib/who_need_help_web/live/activity_live/show.ex:571 +#: lib/who_need_help_web/live/activity_live/show.ex:630 #: lib/who_need_help_web/live/profile_live.ex:276 #: lib/who_need_help_web/live/request_live/index.ex:333 -#: lib/who_need_help_web/live/request_live/show.ex:734 -#: lib/who_need_help_web/live/request_live/show.ex:931 +#: lib/who_need_help_web/live/request_live/show.ex:739 +#: lib/who_need_help_web/live/request_live/show.ex:944 #, elixir-autogen, elixir-format msgid "verified" msgstr "підтверджено" @@ -2727,18 +2711,18 @@ msgstr "підтверджено" msgid "verified handovers" msgstr "підтверджені передачі" -#: lib/who_need_help_web/live/request_live/show.ex:660 +#: lib/who_need_help_web/live/request_live/show.ex:665 #, elixir-autogen, elixir-format msgid "← All requests" msgstr "← Усі заявки" -#: lib/who_need_help_web/live/activity_live/new.ex:113 -#: lib/who_need_help_web/live/activity_live/show.ex:348 +#: lib/who_need_help_web/live/activity_live/new.ex:118 +#: lib/who_need_help_web/live/activity_live/show.ex:352 #, elixir-autogen, elixir-format msgid "← Back to activities" msgstr "← Назад до активностей" -#: lib/who_need_help_web/live/request_live/new.ex:152 +#: lib/who_need_help_web/live/request_live/new.ex:157 #, elixir-autogen, elixir-format msgid "← Back to requests" msgstr "← Назад до заявок" @@ -2799,15 +2783,15 @@ msgstr "Повторювана пара учасників" msgid "Unusual action rate" msgstr "Незвична частота дій" -#: lib/who_need_help_web/live/activity_live/show.ex:448 -#: lib/who_need_help_web/live/request_live/show.ex:768 +#: lib/who_need_help_web/live/activity_live/show.ex:452 +#: lib/who_need_help_web/live/request_live/show.ex:773 #, elixir-autogen, elixir-format msgid "Load earlier messages" msgstr "Завантажити попередні повідомлення" #: lib/who_need_help_web/live/activity_live/index.ex:273 #: lib/who_need_help_web/live/activity_live/index.ex:310 -#: lib/who_need_help_web/live/category_proposal_live.ex:162 +#: lib/who_need_help_web/live/category_proposal_live.ex:168 #: lib/who_need_help_web/live/leaderboard_live.ex:87 #: lib/who_need_help_web/live/moderation_live.ex:384 #: lib/who_need_help_web/live/moderation_live.ex:419 @@ -2822,9 +2806,9 @@ msgid "Load more" msgstr "Завантажити ще" #: lib/who_need_help_web/live/activity_live/index.ex:284 -#: lib/who_need_help_web/live/activity_live/show.ex:498 +#: lib/who_need_help_web/live/activity_live/show.ex:502 #: lib/who_need_help_web/live/request_live/index.ex:314 -#: lib/who_need_help_web/live/request_live/show.ex:849 +#: lib/who_need_help_web/live/request_live/show.ex:862 #, elixir-autogen, elixir-format msgid "The map is unavailable in this browser. Request details remain usable." msgstr "Карта недоступна в цьому браузері. Решта інформації залишається доступною." @@ -2845,19 +2829,19 @@ msgstr "Підтвердьте новий email для свого обліков msgid "Could not complete moderation. Please try again." msgstr "Не вдалося завершити модерацію. Спробуйте ще раз." -#: lib/who_need_help_web/live/activity_live/show.ex:336 -#: lib/who_need_help_web/live/category_proposal_live.ex:60 -#: lib/who_need_help_web/live/request_live/show.ex:621 +#: lib/who_need_help_web/live/activity_live/show.ex:340 +#: lib/who_need_help_web/live/category_proposal_live.ex:61 +#: lib/who_need_help_web/live/request_live/show.ex:626 #, elixir-autogen, elixir-format msgid "Could not complete the action. Please try again." msgstr "Не вдалося завершити дію. Спробуйте ще раз." -#: lib/who_need_help_web/live/activity_live/new.ex:105 +#: lib/who_need_help_web/live/activity_live/new.ex:110 #, elixir-autogen, elixir-format msgid "Could not publish the activity. Please try again." msgstr "Не вдалося опублікувати активність. Спробуйте ще раз." -#: lib/who_need_help_web/live/request_live/new.ex:144 +#: lib/who_need_help_web/live/request_live/new.ex:149 #, elixir-autogen, elixir-format msgid "Could not publish the request. Please try again." msgstr "Не вдалося опублікувати заявку. Спробуйте ще раз." @@ -2872,32 +2856,32 @@ msgstr "Не вдалося розблокувати користувача. С msgid "JavaScript is required to confirm this protected email link." msgstr "Для підтвердження цього захищеного посилання на email потрібен JavaScript." -#: lib/who_need_help_web/live/request_live/show.ex:594 +#: lib/who_need_help_web/live/request_live/show.ex:599 #, elixir-autogen, elixir-format msgid "Last update: %{time}" msgstr "Останнє оновлення: %{time}" -#: lib/who_need_help_web/live/request_live/show.ex:617 +#: lib/who_need_help_web/live/request_live/show.ex:622 #, elixir-autogen, elixir-format msgid "Live location sharing is not active." msgstr "Передавання геопозиції зараз не активне." -#: lib/who_need_help_web/live/request_live/show.ex:592 +#: lib/who_need_help_web/live/request_live/show.ex:597 #, elixir-autogen, elixir-format msgid "Shared location" msgstr "Передавана геопозиція" -#: lib/who_need_help_web/live/activity_live/show.ex:329 +#: lib/who_need_help_web/live/activity_live/show.ex:333 #, elixir-autogen, elixir-format msgid "The organizer cannot leave the activity." msgstr "Організатор не може залишити активність." -#: lib/who_need_help_web/live/activity_live/show.ex:326 +#: lib/who_need_help_web/live/activity_live/show.ex:330 #, elixir-autogen, elixir-format msgid "The organizer is already attending." msgstr "Організатор уже бере участь." -#: lib/who_need_help_web/controllers/user_registration_controller.ex:60 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:58 #, elixir-autogen, elixir-format msgid "The registration form is invalid." msgstr "Форму реєстрації заповнено неправильно." @@ -2917,22 +2901,22 @@ msgstr "Форму налаштувань заповнено неправиль msgid "The sign-in form is invalid." msgstr "Форму входу заповнено неправильно." -#: lib/who_need_help_web/live/activity_live/show.ex:323 +#: lib/who_need_help_web/live/activity_live/show.ex:327 #, elixir-autogen, elixir-format msgid "This activity is no longer available." msgstr "Ця активність більше недоступна." -#: lib/who_need_help_web/live/request_live/show.ex:616 +#: lib/who_need_help_web/live/request_live/show.ex:621 #, elixir-autogen, elixir-format msgid "This match is no longer active." msgstr "Це зіставлення більше не активне." -#: lib/who_need_help_web/live/category_proposal_live.ex:57 +#: lib/who_need_help_web/live/category_proposal_live.ex:58 #, elixir-autogen, elixir-format msgid "This proposal is no longer available." msgstr "Ця пропозиція більше недоступна." -#: lib/who_need_help_web/live/request_live/show.ex:614 +#: lib/who_need_help_web/live/request_live/show.ex:619 #, elixir-autogen, elixir-format msgid "This request has expired." msgstr "Термін дії цієї заявки минув." @@ -2947,12 +2931,12 @@ msgstr "Забагато спроб входу протягом налаштов msgid "Your secure email link is ready. Continue to sign in." msgstr "Захищене посилання з листа готове. Продовжте вхід." -#: lib/who_need_help_web/controllers/user_settings_controller.ex:128 +#: lib/who_need_help_web/controllers/user_settings_controller.ex:130 #, elixir-autogen, elixir-format msgid "The confirmation email could not be sent. Please try again." msgstr "Не вдалося надіслати лист із підтвердженням. Спробуйте ще раз." -#: lib/who_need_help_web/controllers/user_registration_controller.ex:109 +#: lib/who_need_help_web/controllers/user_registration_controller.ex:107 #, elixir-autogen, elixir-format msgid "If this address can be registered or signed in, login instructions will arrive shortly." msgstr "Якщо цю адресу можна зареєструвати або використати для входу, інструкції для входу незабаром надійдуть." @@ -2960,9 +2944,191 @@ msgstr "Якщо цю адресу можна зареєструвати або #: lib/who_need_help_web/live/profile_live.ex:62 #, elixir-autogen, elixir-format msgid "This account cannot perform that action." -msgstr "" +msgstr "Цей обліковий запис не може виконати цю дію." #: lib/who_need_help_web/controllers/user_settings_controller.ex:40 #, elixir-autogen, elixir-format msgid "Too many email-change requests in the configured time window." msgstr "Забагато запитів на зміну email протягом налаштованого проміжку часу." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:32 +#, elixir-autogen, elixir-format +msgid "A Google account is connected. You can use it to sign in." +msgstr "Обліковий запис Google підключено. Тепер за його допомогою можна входити." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:207 +#, elixir-autogen, elixir-format +msgid "A different Google account is already connected." +msgstr "Уже підключено інший обліковий запис Google." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:17 +#, elixir-autogen, elixir-format +msgid "A password is not required. We will email a one-time confirmation link. You can add a password later in account settings." +msgstr "Пароль не обов’язковий. Ми надішлемо на email одноразове посилання для підтвердження. Пароль можна додати пізніше в налаштуваннях облікового запису." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:170 +#, elixir-autogen, elixir-format +msgid "An account with this email already exists. Sign in by email or password, then connect Google in account settings." +msgstr "Обліковий запис із таким email уже існує. Увійдіть за email або паролем, а потім підключіть Google у налаштуваннях облікового запису." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:37 +#, elixir-autogen, elixir-format +msgid "Confirm that you are 18 or older and accept the safety rules first." +msgstr "Спочатку підтвердьте, що вам уже виповнилося 18 років, і прийміть правила безпеки." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:49 +#, elixir-autogen, elixir-format +msgid "Connect Google account" +msgstr "Підключити обліковий запис Google" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Connect Google only after signing in here. We never merge accounts automatically by matching email addresses." +msgstr "Підключайте Google лише після входу тут. Ми ніколи не об’єднуємо облікові записи автоматично за збігом email." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "Create account and email me a link" +msgstr "Створити обліковий запис і надіслати посилання на email" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:99 +#, elixir-autogen, elixir-format +msgid "Email me a sign-in link" +msgstr "Надіслати посилання для входу на email" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:215 +#, elixir-autogen, elixir-format +msgid "Google account connection session is invalid." +msgstr "Сесія підключення облікового запису Google недійсна." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:97 +#, elixir-autogen, elixir-format +msgid "Google did not provide a verified email address." +msgstr "Google не надав підтверджену адресу email." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:29 +#, elixir-autogen, elixir-format +msgid "Google sign-in" +msgstr "Вхід через Google" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:197 +#, elixir-autogen, elixir-format +msgid "Google sign-in connected." +msgstr "Вхід через Google підключено." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:123 +#, elixir-autogen, elixir-format +msgid "Google sign-in could not be started." +msgstr "Не вдалося розпочати вхід через Google." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:102 +#, elixir-autogen, elixir-format +msgid "Google sign-in failed. Please try again." +msgstr "Не вдалося увійти через Google. Спробуйте ще раз." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:128 +#, elixir-autogen, elixir-format +msgid "Google sign-in is not configured yet." +msgstr "Вхід через Google ще не налаштовано." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:87 +#, elixir-autogen, elixir-format +msgid "Google sign-in session expired. Please start again." +msgstr "Сесія входу через Google завершилася. Почніть заново." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:91 +#, elixir-autogen, elixir-format +msgid "Google sign-in session is invalid." +msgstr "Сесія входу через Google недійсна." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:47 +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Google sign-in will be available after the operator configures it." +msgstr "Вхід через Google стане доступним після налаштування оператором." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 +#, elixir-autogen, elixir-format +msgid "Sending link..." +msgstr "Надсилаємо посилання..." + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:74 +#, elixir-autogen, elixir-format +msgid "Sign in with Google" +msgstr "Увійти через Google" + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Sign up with Google" +msgstr "Зареєструватися через Google" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:202 +#, elixir-autogen, elixir-format +msgid "That Google account belongs to another local account." +msgstr "Цей обліковий запис Google належить іншому локальному обліковому запису." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:45 +#, elixir-autogen, elixir-format +msgid "The Google registration form is invalid." +msgstr "Форму реєстрації через Google заповнено неправильно." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:260 +#, elixir-autogen, elixir-format +msgid "This Google account cannot be used right now." +msgstr "Цей обліковий запис Google зараз не можна використовувати." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:144 +#, elixir-autogen, elixir-format +msgid "This Google account is not connected yet. Create an account or sign in by email and connect Google in account settings." +msgstr "Цей обліковий запис Google ще не підключено. Створіть обліковий запис або увійдіть за email і підключіть Google у налаштуваннях." + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:109 +#, elixir-autogen, elixir-format +msgid "This works only if you previously added a password in account settings." +msgstr "Це працює, лише якщо ви раніше додали пароль у налаштуваннях облікового запису." + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:105 +#, elixir-autogen, elixir-format +msgid "Use a password instead" +msgstr "Увійти за паролем" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:96 +#, elixir-autogen, elixir-format +msgid "We will send a one-time sign-in link. No password is required." +msgstr "Ми надішлемо одноразове посилання для входу. Пароль не потрібен." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:163 +#, elixir-autogen, elixir-format +msgid "Your account was created with Google." +msgstr "Ваш обліковий запис створено через Google." + +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:51 +#, elixir-autogen, elixir-format +msgid "or sign up with email" +msgstr "або зареєструватися за email" + +#: lib/who_need_help_web/controllers/user_session_html/new.html.heex:82 +#, elixir-autogen, elixir-format +msgid "or use email" +msgstr "або використати email" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Disconnect Google" +msgstr "Відключити Google" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Disconnect Google sign-in from this account?" +msgstr "Відключити вхід через Google для цього облікового запису?" + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:60 +#, elixir-autogen, elixir-format +msgid "Google sign-in disconnected." +msgstr "Вхід через Google відключено." + +#: lib/who_need_help_web/controllers/google_auth_controller.ex:65 +#, elixir-autogen, elixir-format +msgid "No Google account is connected." +msgstr "Обліковий запис Google не підключено." diff --git a/priv/repo/migrations/20260720200749_create_auth_identities.exs b/priv/repo/migrations/20260720200749_create_auth_identities.exs new file mode 100644 index 0000000..f096de4 --- /dev/null +++ b/priv/repo/migrations/20260720200749_create_auth_identities.exs @@ -0,0 +1,18 @@ +defmodule WhoNeedHelp.Repo.Migrations.CreateAuthIdentities do + use Ecto.Migration + + def change do + create table(:auth_identities, primary_key: false) do + add :id, :binary_id, primary_key: true + add :provider, :string, null: false + add :provider_uid, :string, null: false + add :email, :string, null: false + add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false + + timestamps(type: :utc_datetime) + end + + create unique_index(:auth_identities, [:provider, :provider_uid]) + create unique_index(:auth_identities, [:user_id, :provider]) + end +end diff --git a/scripts/clean-deploy-verify.sh b/scripts/clean-deploy-verify.sh index bb50865..d2aaa5f 100755 --- a/scripts/clean-deploy-verify.sh +++ b/scripts/clean-deploy-verify.sh @@ -154,6 +154,8 @@ PHX_URL_PORT=80 MAP_TILE_URL=https://tile.openstreetmap.org/{z}/{x}/{y}.png GITHUB_OAUTH_CLIENT_ID= GITHUB_OAUTH_CLIENT_SECRET= +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= PUSH_HTTP_ENDPOINT= PUSH_HTTP_BEARER_TOKEN= POSTGRES_DB=$postgres_db diff --git a/scripts/ensure-local-e2e-env.sh b/scripts/ensure-local-e2e-env.sh index 7d2c991..b0bf22f 100755 --- a/scripts/ensure-local-e2e-env.sh +++ b/scripts/ensure-local-e2e-env.sh @@ -85,6 +85,8 @@ CODEX_SESSION_ID=local-e2e RATE_LIMIT_POLICIES_JSON={} GITHUB_OAUTH_CLIENT_ID= GITHUB_OAUTH_CLIENT_SECRET= +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= E2E_BASE_URL=https://proxy E2E_MAILPIT_URL=http://mailpit:8025 E2E_OUTPUT_DIR=$ROOT/output/e2e/generated-per-run diff --git a/scripts/external-boundaries-run.sh b/scripts/external-boundaries-run.sh index 563dbdc..0801bcc 100755 --- a/scripts/external-boundaries-run.sh +++ b/scripts/external-boundaries-run.sh @@ -146,6 +146,12 @@ jq -e ' .oauth.fresh_flow_retry_after_temporary_failure == "passed" and .oauth.timeout_failed_closed == true and .oauth.access_token_returned_to_application == false and + .google_oidc.success == "passed" and + .google_oidc.state_mismatch_blocked_before_token == true and + .google_oidc.one_time_code_replay_rejected == true and + .google_oidc.nonce_mismatch_rejected_after_signature_verification == true and + .google_oidc.unverified_email_rejected == true and + .google_oidc.access_token_returned_to_application == false and .smtp.success == "passed" and .smtp.permanent_rejection_not_retried == true and .smtp.temporary_greeting_retried_once == true and diff --git a/scripts/quality.sh b/scripts/quality.sh index cc07334..d682a27 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -103,6 +103,16 @@ PRODUCTION_EMAIL_FROM_ADDRESS=contact@help.test \ ./scripts/init-production-env.sh help.test "$production_env" >/dev/null test "$(stat -c '%a' "$production_env")" = 600 ./scripts/validate-production-env.sh "$production_env" help.test >/dev/null +partial_google_env="$scan_dir/.env.production.partial-google" +cp "$production_env" "$partial_google_env" +chmod 600 "$partial_google_env" +sed -i 's/^GOOGLE_OAUTH_CLIENT_ID=.*/GOOGLE_OAUTH_CLIENT_ID=quality-client/' \ + "$partial_google_env" +if ./scripts/validate-production-env.sh \ + "$partial_google_env" help.test >/dev/null 2>&1; then + echo "Production environment validator accepted partial Google OAuth credentials." >&2 + exit 1 +fi if ./scripts/init-production-env.sh help.test "$production_env" >/dev/null 2>&1; then echo "Production environment initializer overwrote an existing file." >&2 exit 1 diff --git a/scripts/rotate-local-secrets.sh b/scripts/rotate-local-secrets.sh index 0688aed..f42c4f2 100755 --- a/scripts/rotate-local-secrets.sh +++ b/scripts/rotate-local-secrets.sh @@ -4,6 +4,15 @@ set -eu ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) ENV_FILE="$ROOT/.env" TEST_IMAGE="${TEST_IMAGE:-who-need-help:test}" +MODE=${1:-all} + +case "$MODE" in + all | --database-only) ;; + *) + echo "Usage: $0 [--database-only]" >&2 + exit 1 + ;; +esac if [ ! -f "$ENV_FILE" ]; then echo "Missing $ENV_FILE. Copy .env.example to .env first." >&2 @@ -37,9 +46,6 @@ generate_phoenix_secret() { docker run --rm "$TEST_IMAGE" mix phx.gen.secret } -new_secret_key_base=$(generate_phoenix_secret) -new_handover_secret=$(generate_phoenix_secret) -new_release_cookie=$(generate_phoenix_secret) new_postgres_password=$(openssl rand -hex 32) new_database_url="ecto://${POSTGRES_USER}:${new_postgres_password}@db/${POSTGRES_DB}" @@ -47,18 +53,31 @@ tmp_env=$(mktemp "${ENV_FILE}.rotate.XXXXXX") trap 'rm -f "$tmp_env"' EXIT HUP INT TERM chmod 600 "$tmp_env" -NEW_SECRET_KEY_BASE=$new_secret_key_base \ -NEW_HANDOVER_SECRET=$new_handover_secret \ -NEW_RELEASE_COOKIE=$new_release_cookie \ -NEW_POSTGRES_PASSWORD=$new_postgres_password \ -NEW_DATABASE_URL=$new_database_url \ - perl -pe ' - s/^SECRET_KEY_BASE=.*/SECRET_KEY_BASE=$ENV{NEW_SECRET_KEY_BASE}/; - s/^HANDOVER_SECRET=.*/HANDOVER_SECRET=$ENV{NEW_HANDOVER_SECRET}/; - s/^RELEASE_COOKIE=.*/RELEASE_COOKIE=$ENV{NEW_RELEASE_COOKIE}/; - s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$ENV{NEW_POSTGRES_PASSWORD}/; - s/^DATABASE_URL=.*/DATABASE_URL=$ENV{NEW_DATABASE_URL}/; - ' "$ENV_FILE" >"$tmp_env" +if [ "$MODE" = "--database-only" ]; then + NEW_POSTGRES_PASSWORD=$new_postgres_password \ + NEW_DATABASE_URL=$new_database_url \ + perl -pe ' + s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$ENV{NEW_POSTGRES_PASSWORD}/; + s/^DATABASE_URL=.*/DATABASE_URL=$ENV{NEW_DATABASE_URL}/; + ' "$ENV_FILE" >"$tmp_env" +else + new_secret_key_base=$(generate_phoenix_secret) + new_handover_secret=$(generate_phoenix_secret) + new_release_cookie=$(generate_phoenix_secret) + + NEW_SECRET_KEY_BASE=$new_secret_key_base \ + NEW_HANDOVER_SECRET=$new_handover_secret \ + NEW_RELEASE_COOKIE=$new_release_cookie \ + NEW_POSTGRES_PASSWORD=$new_postgres_password \ + NEW_DATABASE_URL=$new_database_url \ + perl -pe ' + s/^SECRET_KEY_BASE=.*/SECRET_KEY_BASE=$ENV{NEW_SECRET_KEY_BASE}/; + s/^HANDOVER_SECRET=.*/HANDOVER_SECRET=$ENV{NEW_HANDOVER_SECRET}/; + s/^RELEASE_COOKIE=.*/RELEASE_COOKIE=$ENV{NEW_RELEASE_COOKIE}/; + s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$ENV{NEW_POSTGRES_PASSWORD}/; + s/^DATABASE_URL=.*/DATABASE_URL=$ENV{NEW_DATABASE_URL}/; + ' "$ENV_FILE" >"$tmp_env" +fi printf "ALTER ROLE \"%s\" WITH PASSWORD '%s';\n" \ "$POSTGRES_USER" "$new_postgres_password" | @@ -70,5 +89,9 @@ mv "$tmp_env" "$ENV_FILE" chmod 600 "$ENV_FILE" trap - EXIT HUP INT TERM -echo "Local application and PostgreSQL secrets rotated without printing their values." +if [ "$MODE" = "--database-only" ]; then + echo "Local PostgreSQL password rotated without printing its value." +else + echo "Local application and PostgreSQL secrets rotated without printing their values." +fi echo "Run docker compose up -d --wait to apply the new application environment." diff --git a/scripts/validate-production-env.sh b/scripts/validate-production-env.sh index 11eaec8..cf6d1f8 100755 --- a/scripts/validate-production-env.sh +++ b/scripts/validate-production-env.sh @@ -41,6 +41,12 @@ read_value() { ' "$env_file" } +optional_value() { + local key=$1 + + read_value "$key" 2>/dev/null || true +} + require_value() { local key=$1 local value @@ -80,10 +86,14 @@ release_cookie=$(require_value RELEASE_COOKIE) metrics_token=$(require_value METRICS_TOKEN) smtp_relay=$(require_value SMTP_RELAY) smtp_port=$(require_value SMTP_PORT) +smtp_username=$(optional_value SMTP_USERNAME) +smtp_password=$(optional_value SMTP_PASSWORD) smtp_auth=$(require_value SMTP_AUTH) smtp_tls=$(require_value SMTP_TLS) smtp_ssl=$(require_value SMTP_SSL) email_from_address=$(require_value EMAIL_FROM_ADDRESS) +google_oauth_client_id=$(optional_value GOOGLE_OAUTH_CLIENT_ID) +google_oauth_client_secret=$(optional_value GOOGLE_OAUTH_CLIENT_SECRET) [[ "$phx_host" == "$expected_domain" ]] || { echo "PHX_HOST does not match EXPECTED_DOMAIN." >&2 @@ -137,11 +147,35 @@ done echo "SMTP_SSL has an unsupported value." >&2 exit 1 } +if [[ -n "$smtp_username" || -n "$smtp_password" ]]; then + [[ -n "$smtp_username" && -n "$smtp_password" ]] || { + echo "SMTP username and password must either both be set or both be empty." >&2 + exit 1 + } +fi +if [[ "$smtp_auth" == always && (-z "$smtp_username" || -z "$smtp_password") ]]; then + echo "SMTP username and password are required when SMTP_AUTH is always." >&2 + exit 1 +fi +if [[ "$smtp_ssl" =~ ^(true|1)$ && "$smtp_tls" != never ]]; then + echo "SMTP_TLS must be never when SMTP_SSL enables an implicit TLS connection." >&2 + exit 1 +fi [[ "$email_from_address" == *@* ]] || { echo "EMAIL_FROM_ADDRESS is not an email address." >&2 exit 1 } +if [[ -n "$google_oauth_client_id" || -n "$google_oauth_client_secret" ]]; then + [[ -n "$google_oauth_client_id" && -n "$google_oauth_client_secret" ]] || { + echo "Google OAuth client ID and secret must either both be set or both be empty." >&2 + exit 1 + } + + reject_marker GOOGLE_OAUTH_CLIENT_ID "$google_oauth_client_id" + reject_marker GOOGLE_OAUTH_CLIENT_SECRET "$google_oauth_client_secret" +fi + secrets=( "$postgres_password" "$secret_key_base" diff --git a/test/support/google_auth_fake.ex b/test/support/google_auth_fake.ex new file mode 100644 index 0000000..da368ca --- /dev/null +++ b/test/support/google_auth_fake.ex @@ -0,0 +1,46 @@ +defmodule WhoNeedHelp.GoogleAuthFake do + @behaviour WhoNeedHelp.GoogleAuth + + @impl true + def enabled?, do: true + + @impl true + def authorize_url(redirect_uri) do + {:ok, + %{ + url: + "https://accounts.google.example/authorize?" <> + URI.encode_query(%{"redirect_uri" => redirect_uri, "state" => "google-test-state"}), + session_params: %{ + state: "google-test-state", + nonce: "google-test-nonce", + code_verifier: "google-test-verifier" + } + }} + end + + @impl true + def callback(_redirect_uri, params, %{state: state}) do + cond do + params["state"] != state -> + {:error, :invalid_state} + + params["code"] in [nil, ""] -> + {:error, :missing_code} + + params["email_verified"] == "false" -> + {:error, :email_not_verified} + + true -> + email = params["email"] || "google-user@example.com" + + {:ok, + %{ + provider_uid: params["uid"] || "google-user-42", + email: email, + email_verified: true, + display_name: params["name"] || "Google Neighbor" + }} + end + end +end diff --git a/test/who_need_help/google_auth_test.exs b/test/who_need_help/google_auth_test.exs new file mode 100644 index 0000000..31cc682 --- /dev/null +++ b/test/who_need_help/google_auth_test.exs @@ -0,0 +1,71 @@ +defmodule WhoNeedHelp.GoogleAuthTest do + use ExUnit.Case, async: true + + alias WhoNeedHelp.GoogleAuth.AssentAdapter + + test "Google authorization uses OIDC state, nonce, PKCE, and identity-only scopes" do + nonce = "session-bound-nonce" + + assert {:ok, %{url: url, session_params: session_params}} = + Assent.Strategy.Google.authorize_url( + client_id: "client", + client_secret: "secret", + redirect_uri: "https://example.test/auth/google/callback", + nonce: nonce, + code_verifier: true, + openid_configuration: %{ + "authorization_endpoint" => "https://accounts.google.test/o/oauth2/v2/auth" + } + ) + + uri = URI.parse(url) + params = URI.decode_query(uri.query) + + assert uri.host == "accounts.google.test" + assert params["redirect_uri"] == "https://example.test/auth/google/callback" + assert params["scope"] == "openid email profile" + assert params["state"] == session_params.state + assert params["nonce"] == nonce + assert session_params.nonce == nonce + assert params["code_challenge_method"] == "S256" + assert is_binary(session_params.code_verifier) + refute Map.has_key?(params, "access_type") + end + + test "normalizes only a verified Google identity and discards token-shaped claims" do + assert {:ok, identity} = + AssentAdapter.normalize_identity(%{ + "sub" => "google-subject-123", + "email" => " Alice@Example.COM ", + "email_verified" => true, + "name" => "Alice Neighbor", + "access_token" => "must-not-leak", + "id_token" => "must-not-leak" + }) + + assert identity == %{ + provider_uid: "google-subject-123", + email: "alice@example.com", + email_verified: true, + display_name: "Alice Neighbor" + } + + refute Map.has_key?(identity, :access_token) + refute Map.has_key?(identity, :id_token) + end + + test "rejects an unverified or incomplete Google email" do + assert {:error, :email_not_verified} = + AssentAdapter.normalize_identity(%{ + "sub" => "subject", + "email" => "alice@example.com", + "email_verified" => false + }) + + assert {:error, :invalid_provider_identity} = + AssentAdapter.normalize_identity(%{ + "sub" => "subject", + "email_verified" => true + }) + end +end diff --git a/test/who_need_help_web/controllers/google_auth_controller_test.exs b/test/who_need_help_web/controllers/google_auth_controller_test.exs new file mode 100644 index 0000000..48c4727 --- /dev/null +++ b/test/who_need_help_web/controllers/google_auth_controller_test.exs @@ -0,0 +1,263 @@ +defmodule WhoNeedHelpWeb.GoogleAuthControllerTest do + use WhoNeedHelpWeb.ConnCase, async: false + + alias WhoNeedHelp.Accounts + alias WhoNeedHelp.Accounts.AuthIdentity + alias WhoNeedHelp.Repo + alias WhoNeedHelp.Trust.AuditEvent + + import WhoNeedHelp.AccountsFixtures + + test "starts a login flow with callback state, nonce, and PKCE session data", %{conn: conn} do + conn = post(conn, ~p"/auth/google/login") + + assert redirected_to(conn) =~ "https://accounts.google.example/authorize?" + + assert %{ + "flow" => "login", + "session_params" => %{ + state: "google-test-state", + nonce: "google-test-nonce", + code_verifier: "google-test-verifier" + } + } = get_session(conn, "google_auth_flow") + end + + test "requires adult and safety consent before starting Google registration", %{conn: conn} do + conn = + post(conn, ~p"/auth/google/register", %{ + "google_registration" => %{"locale" => "en", "terms_accepted" => "false"} + }) + + assert redirected_to(conn) == ~p"/users/register" + assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "18 or older" + assert is_nil(get_session(conn, "google_auth_flow")) + end + + test "registers, confirms, links, and logs in a new verified Google user", %{conn: conn} do + email = unique_user_email() + + conn = + conn + |> post(~p"/auth/google/register", %{ + "google_registration" => %{"locale" => "ru", "terms_accepted" => "true"} + }) + |> recycle() + |> get( + ~p"/auth/google/callback?code=one-time-code&state=google-test-state&uid=google-123&email=#{email}&name=Google%20Helper" + ) + + assert redirected_to(conn) == ~p"/" + assert get_session(conn, :user_token) + assert is_nil(get_session(conn, "google_auth_flow")) + + user = Accounts.get_user_by_email(email) + assert user.confirmed_at + assert user.accepted_terms_at + assert user.locale == "ru" + assert user.display_name == "Google Helper" + assert is_nil(user.hashed_password) + + assert %AuthIdentity{ + provider: :google, + provider_uid: "google-123", + email: ^email, + user_id: user_id + } = Repo.get_by(AuthIdentity, provider: :google, provider_uid: "google-123") + + assert user_id == user.id + end + + test "does not automatically merge an existing email account", %{conn: conn} do + user = user_fixture() + + conn = + conn + |> post(~p"/auth/google/register", %{ + "google_registration" => %{"locale" => "en", "terms_accepted" => "true"} + }) + |> recycle() + |> get( + ~p"/auth/google/callback?code=code&state=google-test-state&uid=new-google&email=#{user.email}" + ) + + assert redirected_to(conn) == ~p"/users/log-in" + assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "already exists" + refute get_session(conn, :user_token) + refute Repo.get_by(AuthIdentity, provider: :google, provider_uid: "new-google") + end + + test "logs in only through an identity already linked to the local account", %{conn: conn} do + user = user_fixture() + + assert {:ok, _identity} = + Accounts.link_google_identity(user, %{ + provider_uid: "returning-google", + email: user.email, + email_verified: true, + display_name: user.display_name + }) + + conn = + conn + |> post(~p"/auth/google/login") + |> recycle() + |> get( + ~p"/auth/google/callback?code=code&state=google-test-state&uid=returning-google&email=#{user.email}" + ) + + assert redirected_to(conn) == ~p"/" + assert get_session(conn, :user_token) + end + + test "does not create an account from the login-only flow", %{conn: conn} do + email = unique_user_email() + + conn = + conn + |> post(~p"/auth/google/login") + |> recycle() + |> get( + ~p"/auth/google/callback?code=code&state=google-test-state&uid=unknown-google&email=#{email}" + ) + + assert redirected_to(conn) == ~p"/users/log-in" + assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "not connected" + refute Accounts.get_user_by_email(email) + refute get_session(conn, :user_token) + end + + test "rejects unverified Google email and consumes the flow session", %{conn: conn} do + conn = + conn + |> post(~p"/auth/google/login") + |> recycle() + |> get(~p"/auth/google/callback?code=code&state=google-test-state&email_verified=false") + + assert redirected_to(conn) == ~p"/users/log-in" + assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "verified email" + assert is_nil(get_session(conn, "google_auth_flow")) + end + + test "callback cannot be replayed", %{conn: conn} do + first = + conn + |> post(~p"/auth/google/login") + |> recycle() + |> get(~p"/auth/google/callback?code=code&state=google-test-state") + + replay = + first + |> recycle() + |> get(~p"/auth/google/callback?code=code&state=google-test-state") + + assert redirected_to(replay) == ~p"/users/log-in" + assert Phoenix.Flash.get(replay.assigns.flash, :error) =~ "session expired" + end + + test "links Google only to the authenticated flow owner and records an audit event", %{ + conn: conn + } do + user = user_fixture() + + conn = + conn + |> log_in_user(user) + |> post(~p"/auth/google/link") + |> recycle() + |> get( + ~p"/auth/google/callback?code=code&state=google-test-state&uid=linked-google&email=linked@example.com" + ) + + assert redirected_to(conn) == ~p"/users/settings" + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "connected" + + identity = Repo.get_by!(AuthIdentity, provider: :google, provider_uid: "linked-google") + assert identity.user_id == user.id + + assert %AuditEvent{actor_id: actor_id, target_id: target_id} = + Repo.get_by!(AuditEvent, + action: "auth_identity.connected", + target_id: identity.id + ) + + assert actor_id == user.id + assert target_id == identity.id + end + + test "rejects a link callback after the authenticated account changes", %{conn: conn} do + first_user = user_fixture() + second_user = user_fixture() + + conn = + conn + |> log_in_user(first_user) + |> post(~p"/auth/google/link") + |> recycle() + |> log_in_user(second_user) + |> get(~p"/auth/google/callback?code=code&state=google-test-state&uid=wrong-owner") + + assert redirected_to(conn) == ~p"/users/log-in" + assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "invalid" + refute Repo.get_by(AuthIdentity, provider: :google, provider_uid: "wrong-owner") + end + + test "requires authentication to start account linking", %{conn: conn} do + conn = post(conn, ~p"/auth/google/link") + assert redirected_to(conn) == ~p"/users/log-in" + end + + test "rejects a link callback after the sudo window expires", %{conn: conn} do + user = user_fixture() + + conn = + conn + |> log_in_user(user) + |> post(~p"/auth/google/link") + + token = get_session(conn, :user_token) + + override_token_authenticated_at( + token, + DateTime.utc_now(:second) |> DateTime.add(-11, :minute) + ) + + conn = + conn + |> recycle() + |> get(~p"/auth/google/callback?code=code&state=google-test-state&uid=stale-google") + + assert redirected_to(conn) == ~p"/users/log-in" + refute Repo.get_by(AuthIdentity, provider: :google, provider_uid: "stale-google") + end + + test "disconnects Google in sudo mode and records the audit event", %{conn: conn} do + user = user_fixture() + + assert {:ok, identity} = + Accounts.link_google_identity(user, %{ + provider_uid: "disconnect-google", + email: user.email, + email_verified: true, + display_name: user.display_name + }) + + conn = + conn + |> log_in_user(user) + |> delete(~p"/auth/google/link") + + assert redirected_to(conn) == ~p"/users/settings" + assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "disconnected" + refute Repo.get(AuthIdentity, identity.id) + + assert %AuditEvent{actor_id: actor_id, target_id: target_id} = + Repo.get_by!(AuditEvent, + action: "auth_identity.disconnected", + target_id: identity.id + ) + + assert actor_id == user.id + assert target_id == identity.id + end +end diff --git a/test/who_need_help_web/controllers/user_registration_controller_test.exs b/test/who_need_help_web/controllers/user_registration_controller_test.exs index 6ea287b..8dead4d 100644 --- a/test/who_need_help_web/controllers/user_registration_controller_test.exs +++ b/test/who_need_help_web/controllers/user_registration_controller_test.exs @@ -12,6 +12,9 @@ defmodule WhoNeedHelpWeb.UserRegistrationControllerTest do assert response =~ "Register" assert response =~ ~p"/users/log-in" assert response =~ ~p"/users/register" + assert response =~ "A password is not required" + assert response =~ "Sign up with Google" + assert response =~ ~p"/auth/google/register" end test "redirects if already logged in", %{conn: conn} do diff --git a/test/who_need_help_web/controllers/user_session_controller_test.exs b/test/who_need_help_web/controllers/user_session_controller_test.exs index a4f20e3..24c64a5 100644 --- a/test/who_need_help_web/controllers/user_session_controller_test.exs +++ b/test/who_need_help_web/controllers/user_session_controller_test.exs @@ -16,7 +16,9 @@ defmodule WhoNeedHelpWeb.UserSessionControllerTest do assert get_resp_header(conn, "cache-control") == ["no-store"] assert response =~ "Log in" assert response =~ ~p"/users/register" - assert response =~ "Log in with email" + assert response =~ "Email me a sign-in link" + assert response =~ "Sign in with Google" + assert response =~ "Use a password instead" assert response =~ ~s(id="magic-link-fragment-form") assert response =~ ~s(hidden) end @@ -30,7 +32,7 @@ defmodule WhoNeedHelpWeb.UserSessionControllerTest do assert html =~ "You need to reauthenticate" refute html =~ "Register" - assert html =~ "Log in with email" + assert html =~ "Email me a sign-in link" assert html =~ ~s(