diff --git a/.env.example b/.env.example index 1279743..c3ff53e 100644 --- a/.env.example +++ b/.env.example @@ -124,6 +124,8 @@ SMTP_TLS=never SMTP_SSL=false EMAIL_FROM_NAME="Who Need Help" EMAIL_FROM_ADDRESS=contact@example.com +# Optional monitored inbox. It receives new-case alerts and is used as Reply-To. +SUPPORT_INBOX_ADDRESS= CODEX_SESSION_ID=copy-the-main-local-codex-session-id # Optional shared PostgreSQL-backed policies. Keep {} until product thresholds are approved. diff --git a/README.md b/README.md index 99651c2..9a37a26 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ thank-you link; money goes directly between users outside the platform. - Bidirectional discovery blocks, scoped reports, account/request/category moderation, abuse-signal review, and audited moderator access to only the conversation linked by a report. +- Separate public support and content-removal intake, including moderation + appeals, account deletion/data requests, a URL-only TAKE IT DOWN form, + verified-contact status links, operator alerts, and audited staff queues. - Optional GPS evidence derived from browser accuracy envelopes. Raw current positions are deleted on stop, terminal match state, or participant block. - PostgreSQL-backed cross-replica action-limit policies configured by the @@ -201,7 +204,11 @@ existing file: Configure the verified reverse-proxy source IP/CIDR and transactional SMTP provider in that file. Email registration and magic-link login are unusable for -real recipients until that relay and its accepted sender are configured. +real recipients until that relay and its accepted sender are configured. Set +the optional `SUPPORT_INBOX_ADDRESS` to a monitored mailbox to receive +metadata-only new-case alerts and make replies return to the support team; the +database queues continue to work when it is empty. See +[the support and content-removal runbook](docs/support-and-content-removal.md). Then validate the file structure and the production Compose render: ```bash diff --git a/compose.yaml b/compose.yaml index 8b0bc18..e2cd864 100644 --- a/compose.yaml +++ b/compose.yaml @@ -26,6 +26,7 @@ x-app-environment: &app-environment SMTP_SSL: ${SMTP_SSL:?Set SMTP_SSL in .env} EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:?Set EMAIL_FROM_NAME in .env} EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:?Set EMAIL_FROM_ADDRESS in .env} + SUPPORT_INBOX_ADDRESS: ${SUPPORT_INBOX_ADDRESS:-} CODEX_SESSION_ID: ${CODEX_SESSION_ID:-not-configured} RATE_LIMIT_POLICIES_JSON: ${RATE_LIMIT_POLICIES_JSON:-{}} MAP_TILE_URL: ${MAP_TILE_URL:-https://tile.openstreetmap.org/{z}/{x}/{y}.png} diff --git a/config/config.exs b/config/config.exs index c9d24ce..ad202df 100644 --- a/config/config.exs +++ b/config/config.exs @@ -11,6 +11,8 @@ config :who_need_help, :mailer_from, name: "Who Need Help", address: "contact@example.com" +config :who_need_help, :support_inbox_address, nil + config :who_need_help, :scopes, user: [ default: true, diff --git a/config/runtime.exs b/config/runtime.exs index d227eaf..593b0cb 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -444,6 +444,19 @@ if config_env() == :prod do name: System.get_env("EMAIL_FROM_NAME", "Who Need Help"), address: System.get_env("EMAIL_FROM_ADDRESS", "contact@example.com") + support_inbox_address = + case System.get_env("SUPPORT_INBOX_ADDRESS") do + value when value in [nil, ""] -> nil + value -> value + end + + if support_inbox_address && + not Regex.match?(~r/^[^@,;\s]+@[^@,;\s]+$/, support_inbox_address) do + raise "SUPPORT_INBOX_ADDRESS must be a single email address without spaces." + end + + config :who_need_help, :support_inbox_address, support_inbox_address + config :who_need_help, WhoNeedHelp.Mailer, smtp_config config :who_need_help, WhoNeedHelpWeb.Endpoint, diff --git a/deploy/helm/who-need-help/values.yaml b/deploy/helm/who-need-help/values.yaml index 6816a8f..93c642f 100644 --- a/deploy/helm/who-need-help/values.yaml +++ b/deploy/helm/who-need-help/values.yaml @@ -40,7 +40,8 @@ app: # 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, and both GOOGLE_OAUTH_CLIENT_ID and -# GOOGLE_OAUTH_CLIENT_SECRET to enable Google registration/sign-in. Push is +# GOOGLE_OAUTH_CLIENT_SECRET to enable Google registration/sign-in. Optional +# SUPPORT_INBOX_ADDRESS enables metadata-only operator alerts and Reply-To. 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 diff --git a/docs/operations.md b/docs/operations.md index 68c04fe..08329e9 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -71,7 +71,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. If Google registration/sign-in is +edited in the resulting ignored file. Set optional `SUPPORT_INBOX_ADDRESS` to +a monitored address for support/removal queue alerts and email `Reply-To`; +leaving it empty disables operator email alerts, not the protected queues. 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: diff --git a/docs/support-and-content-removal.md b/docs/support-and-content-removal.md new file mode 100644 index 0000000..0084360 --- /dev/null +++ b/docs/support-and-content-removal.md @@ -0,0 +1,137 @@ +# Support and content-removal operations + +Status: implemented intake and operator workflow. This document describes the +software behavior; it is not a legal opinion and does not establish that a +particular law applies to the operator. + +## Separate queues + +Who Need Help deliberately separates three mechanisms: + +1. An authenticated in-product report targets exactly one request, assignment, + message, Activity, or Activity message and follows the existing audited + trust-and-safety workflow. +2. A support request covers account access, technical issues, safety concerns, + moderation appeals, privacy questions, data export, and account deletion. +3. A content-removal notice covers a precise item alleged to be illegal or to + infringe a right. TAKE IT DOWN notices have a dedicated public form and a + separate regime value in the removal queue. + +The operator workspace is `/support/operations`. It is protected by the same +database-checked moderator or administrator authorization as the existing +moderation workspace. Every creation and decision records an audit event. + +## Public routes + +- `/support` — ordinary support. +- `/account/delete` — external account-deletion request path suitable for use + as the Google Play account-deletion web resource. +- `/legal/content-removal` — general electronic notice-and-action intake. +- `/legal/take-it-down` — dedicated intimate-visual-material removal intake. + +The request and Activity screens link to the general form with the exact local +content URL prefilled. Only same-origin relative paths can be prefilled this +way. The server validates one complete HTTP or HTTPS URL per line and accepts +at most 20 locations in one notice. + +## Contact verification and status access + +Public submissions display a reference but never expose the signed status token +in the redirect. The private status link is sent to the contact email. Opening +it marks that contact address as verified. An authenticated submission uses the +confirmed account email and is verified immediately. + +Email-link verification establishes access to the mailbox, not government +identity, authority to act for another person, or the truth of the claim. +Operators must perform any additional verification appropriate to the requested +action. In particular, an account-deletion request must not be fulfilled against +another person's account merely because an address was typed into the form. + +Identity and contact fields may be omitted from a general report of sexual +material involving a minor. Such a report still receives an urgent queue state, +but no status email can be sent without a contact address. + +## TAKE IT DOWN boundary + +The dedicated form accepts exact URLs and textual identification only. It does +not accept file uploads and explicitly tells the submitter not to reproduce or +email the intimate image or video. A submitted notice is marked for urgent +review and records a review due time 48 hours after receipt. + +The current product does not accept user-uploaded images or videos. The presence +of this preparedness workflow therefore does not claim that Who Need Help is a +covered platform, that a submitted item satisfies the statutory definition, or +that identical-copy detection exists. If media hosting is added, the operator +must review the then-current law, implement safe media hashing and +identical-copy handling where applicable, and test the entire removal path +before enabling uploads. + +The primary US text currently requires a clear and conspicuous process and, for +a valid request to a covered platform, removal of the depiction plus reasonable +efforts concerning known identical copies as soon as possible and no later than +48 hours: + +- + +## EU electronic notices + +The general form captures the elements listed in Article 16 of Regulation (EU) +2022/2065: a reasoned explanation, exact electronic location, submitter name and +email subject to the child-sexual-abuse exception, and a good-faith accuracy +statement. It sends an acknowledgement when email is available and sends the +recorded decision after a verified-contact operator update. + +- + +Product implementation alone does not determine the service's legal +classification, establishment, target markets, applicable national law, or +redress obligations. Those remain launch decisions requiring jurisdiction- +specific review. + +## Email and operator notification + +`EMAIL_FROM_ADDRESS` remains the outbound sender. Optional +`SUPPORT_INBOX_ADDRESS` has two separate effects: + +- outgoing support/removal messages use it as `Reply-To`; +- it receives a metadata-only alert containing the reference, queue type, and + protected operator URL when a case is created. + +The alert intentionally excludes the free-text report and reported URLs. The +full record remains in the protected database queue. If the variable is empty, +intake and reporter acknowledgement still work, but no operator inbox alert is +sent. The configured inbox must be monitored operationally; the application +cannot prove staffing or response availability. + +## Account deletion and data export + +The web application and Android WebView expose the account-deletion request from +account settings, and the public `/account/delete` route remains usable after an +app is uninstalled. The workflow verifies the contact and creates an audited +account-lifecycle request. + +Actual erasure/anonymization and export are not automated because the operator +has not yet selected a jurisdiction-specific retention policy for safety, +fraud, disputes, and legal records. An operator must not mark a request resolved +until the applicable data action has actually been completed and communicated. +Before public launch, legal review must define which linked records are erased, +anonymized, or retained and for how long; only then should a destructive +execution routine be implemented and tested against backups and relational +constraints. + +Google Play's current policy requires both an in-app path and an external web +resource when an app allows account creation: + +- + +## Abuse controls and operational limits + +`support_request` and `content_removal_notice` are supported names in the shared +PostgreSQL rate limiter. As with the other actions, no numeric policy is enabled +unless the operator supplies measured values through +`RATE_LIMIT_POLICIES_JSON`. CSRF protection, validation, exact URL limits, +contact verification, staff authorization, and audit events apply regardless. + +The software does not provide emergency response. Threats to life or safety are +prioritized in the queue, while every public safety screen continues to direct +people in immediate danger to local emergency services. diff --git a/docs/trust-safety.md b/docs/trust-safety.md index 446187e..ab05344 100644 --- a/docs/trust-safety.md +++ b/docs/trust-safety.md @@ -135,3 +135,10 @@ requires an explicit one-time operational bootstrap, and later role changes are audited; the last administrator cannot demote themselves. The operator must publish jurisdiction-specific emergency contacts, privacy notice, prohibited-items policy, and data-retention policy before public launch. + +Public support and legal removal are separate from in-product reports. Support, +moderation appeals, privacy/data requests, general electronic removal notices, +and a dedicated TAKE IT DOWN intake are described in +`docs/support-and-content-removal.md`. Public contacts are verified through an +emailed private status link; moderator/admin decisions and access remain +audited. The removal forms never accept intimate-media uploads. diff --git a/docs/verification.md b/docs/verification.md index 2ae113a..02a3fdd 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -1,6 +1,6 @@ # Who Need Help — implementation verification -Observed through 2026-07-20 in the local workspace. This report separates observed +Observed through 2026-07-21 in the local workspace. This report separates observed results from product limits and unknown production properties. ## Verified MVP capabilities @@ -18,6 +18,7 @@ results from product limits and unknown production properties. | 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. | | 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. | +| Support and content removal | Implemented and browser-verified | Public support, account deletion, general removal, and TAKE IT DOWN forms create separate audited queues; private email status links verify public contacts; authenticated submissions use the account email; moderator-only operations can update status and notify verified contacts. TAKE IT DOWN accepts URLs/text only and records a 48-hour review due time. | The current product hosts no user media and does not claim TAKE IT DOWN coverage. Staffing, jurisdiction-specific legal classification, final retention rules, actual account erasure/export, and identical-media-copy handling remain operational/legal work. | | 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. | @@ -27,6 +28,16 @@ results from product limits and unknown production properties. ## Reproducible checks +- On 2026-07-21, `./scripts/test.sh` and the Dockerized `mix precommit` each + passed 273 tests after the support/content-removal implementation. The full + isolated `./scripts/quality.sh` gate passed compiler, xref, Credo, Sobelow, + Dialyzer, Hex/npm audits, Compose/Helm validation, and configured source/image + scans. Headed Chrome over `https://whoneedhelp.imalto.site` verified public + support acknowledgement and contact verification, general removal intake, + the separate urgent TAKE IT DOWN form and 48-hour due time, passwordless + account confirmation, admin-only queues, audited operator response email, + account-deletion navigation, Russian localization, selected-status + preservation, and zero browser-console errors or warnings. - 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, @@ -1115,10 +1126,12 @@ availability, or target-server capacity. autoscaling, and action-limit policies. - Publish jurisdiction-specific emergency contacts, privacy, retention, prohibited-items, and voluntary-payment guidance after legal review. -- The current UI has no self-service account deletion or data-export flow. - Decide the retention/anonymization behavior, implement the selected control, - and verify it before promising that capability to users. -- Establish moderation/support operations and incident response for real users. +- The UI now has authenticated and external account-deletion/data-request + intake, contact verification, case status, and an audited operator queue. + Actual erasure/anonymization and export remain manual until a legally reviewed + retention policy defines the treatment of linked safety and dispute records. +- Staff and monitor the implemented moderation/support queues and establish an + incident-response/on-call process for real users. - If verified GitHub identity is desired, create and configure its OAuth App and exercise the external redirect/callback. This is an optional enhancement, not a registration or launch blocker. diff --git a/lib/who_need_help/content_removal.ex b/lib/who_need_help/content_removal.ex new file mode 100644 index 0000000..86f1d7d --- /dev/null +++ b/lib/who_need_help/content_removal.ex @@ -0,0 +1,256 @@ +defmodule WhoNeedHelp.ContentRemoval do + @moduledoc "Separate intake and review queue for electronic content-removal notices." + + import Ecto.Query + + alias WhoNeedHelp.Accounts + alias WhoNeedHelp.Accounts.{Scope, User} + alias WhoNeedHelp.ContentRemoval.{Notice, Notifier} + alias WhoNeedHelp.Pagination + alias WhoNeedHelp.Repo + alias WhoNeedHelp.Trust + alias WhoNeedHelp.Trust.RateLimiter + + @access_salt "content-removal-access" + @urgent_categories [ + :non_consensual_intimate_media, + :child_sexual_abuse_material, + :threat_to_life_or_safety + ] + + def change_notice(%Notice{} = notice, attrs \\ %{}) do + Notice.submission_changeset(notice, attrs) + end + + def create_notice(scope, regime, attrs) when regime in [:general, :dsa, :take_it_down] do + user = scope_user(scope) + attrs = normalize_keys(attrs) + attrs = maybe_use_user_email(attrs, user) + category = enum_value(Notice, :category, attrs["category"]) + status = if category in @urgent_categories, do: :urgent_review, else: :open + + response_due_at = + if regime == :take_it_down, do: DateTime.add(DateTime.utc_now(:second), 48, :hour) + + with {:ok, _limit} <- + RateLimiter.check(:content_removal_notice, rate_scope(user, attrs["contact_email"])) do + Repo.transact(fn -> + with {:ok, notice} <- + %Notice{ + reference: unique_reference(), + regime: regime, + status: status, + response_due_at: response_due_at, + requester_id: user && user.id, + contact_verified_at: user && DateTime.utc_now(:second) + } + |> Notice.submission_changeset(attrs) + |> Repo.insert(), + {:ok, _audit} <- + Trust.audit( + user && user.id, + "content_removal_notice.created", + "content_removal_notice", + notice.id, + %{ + "category" => to_string(notice.category), + "regime" => to_string(notice.regime), + "status" => to_string(notice.status) + } + ) do + {:ok, notice} + end + end) + |> notify_received() + end + end + + def list_for_user(%Scope{user: %User{id: user_id}}) do + Notice + |> where([notice], notice.requester_id == ^user_id) + |> order_by([notice], desc: notice.inserted_at, desc: notice.id) + |> Repo.all() + end + + def get_by_access_token(id, token) when is_binary(token) do + with {:ok, id} <- Ecto.UUID.cast(id), + {:ok, ^id} <- + Phoenix.Token.verify(WhoNeedHelpWeb.Endpoint, @access_salt, token, max_age: 31_536_000), + %Notice{} = notice <- Repo.get(Notice, id) do + verify_contact(notice) + else + _ -> {:error, :not_found} + end + end + + def get_by_access_token(_id, _token), do: {:error, :not_found} + + def access_token(%Notice{id: id}) do + Phoenix.Token.sign(WhoNeedHelpWeb.Endpoint, @access_salt, id) + end + + def status_url(%Notice{} = notice) do + token = access_token(notice) + + WhoNeedHelpWeb.Endpoint.url() <> + "/legal/content-removal/#{notice.id}?token=#{URI.encode_www_form(token)}" + end + + def paginate_for_staff(%Scope{user: user}, options \\ []) do + if Accounts.moderator_authorized?(user) do + limit = Pagination.limit(options) + cursor = Pagination.cursor(options) + + Notice + |> maybe_regime(Keyword.get(options, :regime)) + |> before(cursor) + |> order_by([notice], desc: notice.inserted_at, desc: notice.id) + |> limit(^(limit + 1)) + |> preload([:requester, :reviewed_by]) + |> Repo.all() + |> Pagination.page(limit, &{&1.inserted_at, &1.id}) + else + %Pagination.Page{} + end + end + + def moderate(%Scope{user: moderator}, id, attrs) do + with {:ok, id} <- Ecto.UUID.cast(id), + true <- Accounts.moderator_authorized?(moderator) do + attrs = + attrs + |> normalize_keys() + |> Map.merge(%{ + "reviewed_at" => DateTime.utc_now(:second), + "reviewed_by_id" => moderator.id + }) + + Repo.transact(fn -> + notice = + Notice + |> where([notice], notice.id == ^id) + |> lock("FOR UPDATE") + |> Repo.one() + + if notice do + with {:ok, notice} <- notice |> Notice.moderation_changeset(attrs) |> Repo.update(), + {:ok, _audit} <- + Trust.audit( + moderator.id, + "content_removal_notice.moderated", + "content_removal_notice", + notice.id, + %{ + "status" => to_string(notice.status) + } + ) do + {:ok, notice} + end + else + {:error, :not_found} + end + end) + |> notify_decision() + else + false -> {:error, :forbidden} + _ -> {:error, :not_found} + end + end + + defp notify_received({:ok, %Notice{contact_email: email} = notice}) when email in [nil, ""], + do: notify_operator(notice) + + defp notify_received({:ok, notice}) do + result = + case Notifier.deliver_received(notice, status_url(notice)) do + {:ok, _metadata} -> + notice + |> Ecto.Changeset.change(acknowledgement_sent_at: DateTime.utc_now(:second)) + |> Repo.update() + + _error -> + {:ok, notice} + end + + case result do + {:ok, current} -> notify_operator(current) + error -> error + end + end + + defp notify_received(result), do: result + + defp notify_operator(notice) do + _ = Notifier.deliver_operator_alert(notice) + {:ok, notice} + end + + defp notify_decision({:ok, %Notice{contact_verified_at: nil} = notice}), do: {:ok, notice} + + defp notify_decision({:ok, notice}) do + case Notifier.deliver_decision(notice, status_url(notice)) do + {:ok, _metadata} -> + notice + |> Ecto.Changeset.change(decision_sent_at: DateTime.utc_now(:second)) + |> Repo.update() + + _error -> + {:ok, notice} + end + end + + defp notify_decision(result), do: result + + defp verify_contact(%Notice{contact_verified_at: nil} = notice) do + notice + |> Ecto.Changeset.change(contact_verified_at: DateTime.utc_now(:second)) + |> Repo.update() + end + + defp verify_contact(%Notice{} = notice), do: {:ok, notice} + + defp before(query, nil), do: query + + defp before(query, {inserted_at, id}) do + where( + query, + [notice], + notice.inserted_at < ^inserted_at or + (notice.inserted_at == ^inserted_at and notice.id < ^id) + ) + end + + defp maybe_regime(query, nil), do: query + defp maybe_regime(query, regime), do: where(query, [notice], notice.regime == ^regime) + + defp maybe_use_user_email(attrs, %User{email: email}) do + Map.put(attrs, "contact_email", email) + end + + defp maybe_use_user_email(attrs, nil), do: attrs + + defp rate_scope(%User{id: id}, _email), do: "user:#{id}" + + defp rate_scope(nil, email) when is_binary(email), + do: "email:#{String.downcase(String.trim(email))}" + + defp rate_scope(nil, _email), do: "missing-email" + + defp scope_user(%Scope{user: %User{} = user}), do: user + defp scope_user(_scope), do: nil + + defp enum_value(module, field, value) do + Ecto.Enum.cast_value(module, field, value) + |> case do + {:ok, enum} -> enum + :error -> nil + end + end + + defp unique_reference do + suffix = 8 |> :crypto.strong_rand_bytes() |> Base.encode32(case: :upper, padding: false) + "REM-#{suffix}" + end + + defp normalize_keys(attrs), do: Map.new(attrs, fn {key, value} -> {to_string(key), value} end) +end diff --git a/lib/who_need_help/content_removal/notice.ex b/lib/who_need_help/content_removal/notice.ex new file mode 100644 index 0000000..558476f --- /dev/null +++ b/lib/who_need_help/content_removal/notice.ex @@ -0,0 +1,177 @@ +defmodule WhoNeedHelp.ContentRemoval.Notice do + use Ecto.Schema + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + + schema "content_removal_notices" do + field :reference, :string + + field :regime, Ecto.Enum, + values: [:general, :dsa, :take_it_down], + default: :general + + field :category, Ecto.Enum, + values: [ + :illegal_content, + :privacy_violation, + :copyright, + :impersonation, + :non_consensual_intimate_media, + :child_sexual_abuse_material, + :threat_to_life_or_safety, + :other + ] + + field :status, Ecto.Enum, + values: [ + :open, + :urgent_review, + :reviewing, + :needs_information, + :actioned, + :rejected, + :closed + ], + default: :open + + field :submitter_name, :string + field :contact_email, :string + + field :relationship, Ecto.Enum, values: [:self, :authorized_representative, :other] + + field :content_locations, :string + field :explanation, :string + field :legal_basis, :string + field :contact_verified_at, :utc_datetime + field :good_faith, :boolean, default: false + field :accurate_complete, :boolean, default: false + field :electronic_signature, :string + field :resolution_note, :string + field :reviewed_at, :utc_datetime + field :response_due_at, :utc_datetime + field :acknowledgement_sent_at, :utc_datetime + field :decision_sent_at, :utc_datetime + belongs_to :requester, WhoNeedHelp.Accounts.User + belongs_to :reviewed_by, WhoNeedHelp.Accounts.User + timestamps(type: :utc_datetime) + end + + def submission_changeset(notice, attrs) do + notice + |> cast(attrs, [ + :category, + :submitter_name, + :contact_email, + :relationship, + :content_locations, + :explanation, + :legal_basis, + :good_faith, + :accurate_complete, + :electronic_signature + ]) + |> validate_required([ + :category, + :content_locations, + :explanation, + :reference, + :regime + ]) + |> update_change(:contact_email, &normalize_email/1) + |> validate_format(:contact_email, ~r/^[^@,;\s]+@[^@,;\s]+$/, + message: "must have the @ sign and no spaces" + ) + |> validate_length(:submitter_name, min: 2, max: 160) + |> validate_length(:contact_email, max: 160) + |> validate_length(:content_locations, min: 5, max: 10_000) + |> validate_length(:explanation, min: 20, max: 10_000) + |> validate_length(:legal_basis, max: 2_000) + |> validate_length(:electronic_signature, min: 2, max: 160) + |> validate_acceptance(:good_faith, + message: "you must confirm your good-faith belief" + ) + |> validate_acceptance(:accurate_complete, + message: "you must confirm that the notice is accurate and complete" + ) + |> validate_identity_requirements() + |> validate_content_locations() + |> validate_take_it_down_category() + |> unique_constraint(:reference) + end + + def moderation_changeset(notice, attrs) do + notice + |> cast(attrs, [:status, :resolution_note, :reviewed_at, :reviewed_by_id, :decision_sent_at]) + |> validate_required([:status, :reviewed_at, :reviewed_by_id]) + |> validate_length(:resolution_note, max: 10_000) + end + + def content_urls(%__MODULE__{content_locations: locations}) when is_binary(locations) do + locations + |> String.split(~r/[\r\n]+/, trim: true) + |> Enum.map(&String.trim/1) + end + + def content_urls(_notice), do: [] + + defp validate_content_locations(changeset) do + validate_change(changeset, :content_locations, fn :content_locations, locations -> + urls = locations |> String.split(~r/[\r\n]+/, trim: true) |> Enum.map(&String.trim/1) + + cond do + urls == [] -> + [content_locations: "must contain at least one exact URL"] + + length(urls) > 20 -> + [content_locations: "cannot contain more than 20 URLs"] + + Enum.any?(urls, &(not valid_http_url?(&1))) -> + [content_locations: "must contain one full HTTP or HTTPS URL per line"] + + true -> + [] + end + end) + end + + defp validate_take_it_down_category(changeset) do + if get_field(changeset, :regime) == :take_it_down and + get_field(changeset, :category) not in [ + :non_consensual_intimate_media, + :child_sexual_abuse_material + ] do + add_error(changeset, :category, "must describe intimate visual material") + else + changeset + end + end + + defp validate_identity_requirements(changeset) do + if get_field(changeset, :category) == :child_sexual_abuse_material and + get_field(changeset, :regime) != :take_it_down do + changeset + else + validate_required(changeset, [ + :submitter_name, + :contact_email, + :relationship, + :electronic_signature + ]) + end + end + + defp valid_http_url?(value) do + case URI.parse(value) do + %URI{scheme: scheme, host: host} + when scheme in ["http", "https"] and is_binary(host) and host != "" -> + true + + _ -> + false + end + end + + defp normalize_email(email), do: email |> String.trim() |> String.downcase() +end diff --git a/lib/who_need_help/content_removal/notifier.ex b/lib/who_need_help/content_removal/notifier.ex new file mode 100644 index 0000000..ded33fe --- /dev/null +++ b/lib/who_need_help/content_removal/notifier.ex @@ -0,0 +1,91 @@ +defmodule WhoNeedHelp.ContentRemoval.Notifier do + import Swoosh.Email + + alias WhoNeedHelp.ContentRemoval.Notice + alias WhoNeedHelp.Mailer + + def deliver_received(%Notice{contact_email: email} = notice, status_url) + when is_binary(email) and email != "" do + deliver( + email, + "Who Need Help removal notice #{notice.reference}", + """ + We received removal notice #{notice.reference}. + + Status: #{notice.status} + + View the current status: + #{status_url} + + Do not reply with or upload intimate visual material. If someone is in immediate danger, + contact the emergency service for their location. + """ + ) + end + + def deliver_received(%Notice{}, _status_url), do: {:ok, :no_contact_email} + + def deliver_decision(%Notice{contact_email: email} = notice, status_url) + when is_binary(email) and email != "" do + deliver( + email, + "Who Need Help removal notice update #{notice.reference}", + """ + Removal notice #{notice.reference} was updated. + + Status: #{notice.status} + Decision or request for information: + #{notice.resolution_note || "No additional response was provided."} + + View the current status: + #{status_url} + """ + ) + end + + def deliver_decision(%Notice{}, _status_url), do: {:ok, :no_contact_email} + + def deliver_operator_alert(%Notice{} = notice) do + case Application.get_env(:who_need_help, :support_inbox_address) do + address when is_binary(address) and address != "" -> + urgency = if notice.status == :urgent_review, do: "URGENT: ", else: "" + + deliver( + address, + "#{urgency}New removal notice #{notice.reference}", + """ + A new #{notice.regime} content-removal notice is waiting for review. + + Reference: #{notice.reference} + Category: #{notice.category} + Review due: #{notice.response_due_at || "no statutory deadline recorded"} + Operator queue: #{WhoNeedHelpWeb.Endpoint.url()}/support/operations + + Do not request the submitter to email or upload intimate visual material. + """ + ) + + _other -> + {:ok, :not_configured} + end + end + + defp deliver(recipient, subject, body) do + from = Application.fetch_env!(:who_need_help, :mailer_from) + + new() + |> to(recipient) + |> from({from[:name], from[:address]}) + |> maybe_reply_to() + |> subject(subject) + |> text_body(body) + |> Mailer.deliver() + end + + defp maybe_reply_to(email) do + case Application.get_env(:who_need_help, :support_inbox_address) do + address when is_binary(address) and address != "" -> reply_to(email, address) + _other -> email + end + end +end diff --git a/lib/who_need_help/support.ex b/lib/who_need_help/support.ex new file mode 100644 index 0000000..71bda36 --- /dev/null +++ b/lib/who_need_help/support.ex @@ -0,0 +1,227 @@ +defmodule WhoNeedHelp.Support do + @moduledoc "Public and authenticated support, privacy, and account-lifecycle requests." + + import Ecto.Query + + alias WhoNeedHelp.Accounts + alias WhoNeedHelp.Accounts.{Scope, User} + alias WhoNeedHelp.Pagination + alias WhoNeedHelp.Repo + alias WhoNeedHelp.Support.{Notifier, SupportRequest} + alias WhoNeedHelp.Trust + alias WhoNeedHelp.Trust.RateLimiter + + @access_salt "support-request-access" + + def change_request(%SupportRequest{} = request, attrs \\ %{}) do + SupportRequest.submission_changeset(request, attrs) + end + + def create_request(scope, attrs) when is_map(attrs) do + user = scope_user(scope) + attrs = normalize_keys(attrs) + contact_email = if user, do: user.email, else: attrs["contact_email"] + attrs = Map.put(attrs, "contact_email", contact_email) + + with {:ok, _limit} <- RateLimiter.check(:support_request, rate_scope(user, contact_email)) do + Repo.transact(fn -> + with {:ok, request} <- + %SupportRequest{ + reference: unique_reference("SUP"), + requester_id: user && user.id, + contact_verified_at: user && DateTime.utc_now(:second) + } + |> SupportRequest.submission_changeset(attrs) + |> Repo.insert(), + {:ok, _audit} <- + Trust.audit( + user && user.id, + "support_request.created", + "support_request", + request.id, + %{ + "kind" => to_string(request.kind) + } + ) do + {:ok, request} + end + end) + |> notify_received() + end + end + + def create_account_deletion_request(%Scope{user: %User{}} = scope, details \\ nil) do + create_request(scope, %{ + "kind" => "account_deletion", + "subject" => "Delete my Who Need Help account", + "details" => details || "Please delete my account and associated personal data." + }) + end + + def list_for_user(%Scope{user: %User{id: user_id}}) do + SupportRequest + |> where([request], request.requester_id == ^user_id) + |> order_by([request], desc: request.inserted_at, desc: request.id) + |> Repo.all() + end + + def get_for_user(%Scope{user: %User{id: user_id}}, id) do + with {:ok, id} <- Ecto.UUID.cast(id), + %SupportRequest{} = request <- + Repo.get_by(SupportRequest, id: id, requester_id: user_id) do + {:ok, request} + else + _ -> {:error, :not_found} + end + end + + def get_by_access_token(id, token) when is_binary(token) do + with {:ok, id} <- Ecto.UUID.cast(id), + {:ok, ^id} <- + Phoenix.Token.verify(WhoNeedHelpWeb.Endpoint, @access_salt, token, max_age: 31_536_000), + %SupportRequest{} = request <- Repo.get(SupportRequest, id) do + verify_contact(request) + else + _ -> {:error, :not_found} + end + end + + def get_by_access_token(_id, _token), do: {:error, :not_found} + + def access_token(%SupportRequest{id: id}) do + Phoenix.Token.sign(WhoNeedHelpWeb.Endpoint, @access_salt, id) + end + + def status_url(%SupportRequest{} = request) do + token = access_token(request) + + WhoNeedHelpWeb.Endpoint.url() <> + "/support/cases/#{request.id}?token=#{URI.encode_www_form(token)}" + end + + def paginate_for_staff(%Scope{user: user}, options \\ []) do + if Accounts.moderator_authorized?(user) do + limit = Pagination.limit(options) + cursor = Pagination.cursor(options) + + SupportRequest + |> maybe_kind(Keyword.get(options, :kind)) + |> before(cursor) + |> order_by([request], desc: request.inserted_at, desc: request.id) + |> limit(^(limit + 1)) + |> preload([:requester, :reviewed_by]) + |> Repo.all() + |> Pagination.page(limit, &{&1.inserted_at, &1.id}) + else + %Pagination.Page{} + end + end + + def moderate(%Scope{user: moderator}, id, attrs) do + with {:ok, id} <- Ecto.UUID.cast(id), + true <- Accounts.moderator_authorized?(moderator) do + attrs = + attrs + |> normalize_keys() + |> Map.merge(%{ + "reviewed_at" => DateTime.utc_now(:second), + "reviewed_by_id" => moderator.id + }) + + Repo.transact(fn -> + request = + SupportRequest + |> where([request], request.id == ^id) + |> lock("FOR UPDATE") + |> Repo.one() + + if request do + with {:ok, request} <- + request |> SupportRequest.moderation_changeset(attrs) |> Repo.update(), + {:ok, _audit} <- + Trust.audit( + moderator.id, + "support_request.moderated", + "support_request", + request.id, + %{ + "status" => to_string(request.status) + } + ) do + {:ok, request} + end + else + {:error, :not_found} + end + end) + |> notify_decision() + else + false -> {:error, :forbidden} + _ -> {:error, :not_found} + end + end + + defp notify_received({:ok, request}) do + _ = Notifier.deliver_received(request, status_url(request)) + _ = Notifier.deliver_operator_alert(request) + {:ok, request} + end + + defp notify_received(result), do: result + + defp notify_decision({:ok, %SupportRequest{contact_verified_at: nil} = request}), + do: {:ok, request} + + defp notify_decision({:ok, request}) do + case Notifier.deliver_decision(request, status_url(request)) do + {:ok, _metadata} -> + request + |> Ecto.Changeset.change(response_sent_at: DateTime.utc_now(:second)) + |> Repo.update() + + _error -> + {:ok, request} + end + end + + defp notify_decision(result), do: result + + defp verify_contact(%SupportRequest{contact_verified_at: nil} = request) do + request + |> Ecto.Changeset.change(contact_verified_at: DateTime.utc_now(:second)) + |> Repo.update() + end + + defp verify_contact(%SupportRequest{} = request), do: {:ok, request} + + defp before(query, nil), do: query + + defp before(query, {inserted_at, id}) do + where( + query, + [request], + request.inserted_at < ^inserted_at or + (request.inserted_at == ^inserted_at and request.id < ^id) + ) + end + + defp maybe_kind(query, nil), do: query + defp maybe_kind(query, kind), do: where(query, [request], request.kind == ^kind) + + defp rate_scope(%User{id: id}, _email), do: "user:#{id}" + + defp rate_scope(nil, email) when is_binary(email), + do: "email:#{String.downcase(String.trim(email))}" + + defp rate_scope(nil, _email), do: "missing-email" + + defp scope_user(%Scope{user: %User{} = user}), do: user + defp scope_user(_scope), do: nil + + defp unique_reference(prefix) do + suffix = 8 |> :crypto.strong_rand_bytes() |> Base.encode32(case: :upper, padding: false) + "#{prefix}-#{suffix}" + end + + defp normalize_keys(attrs), do: Map.new(attrs, fn {key, value} -> {to_string(key), value} end) +end diff --git a/lib/who_need_help/support/notifier.ex b/lib/who_need_help/support/notifier.ex new file mode 100644 index 0000000..4059f59 --- /dev/null +++ b/lib/who_need_help/support/notifier.ex @@ -0,0 +1,81 @@ +defmodule WhoNeedHelp.Support.Notifier do + import Swoosh.Email + + alias WhoNeedHelp.Mailer + alias WhoNeedHelp.Support.SupportRequest + + def deliver_received(%SupportRequest{} = request, status_url) do + deliver( + request.contact_email, + "Who Need Help support request #{request.reference}", + """ + We received your support request #{request.reference}. + + Subject: #{request.subject} + Status: #{request.status} + + View the current status: + #{status_url} + + If there is immediate danger, contact the emergency service for your location. + """ + ) + end + + def deliver_decision(%SupportRequest{} = request, status_url) do + deliver( + request.contact_email, + "Who Need Help support update #{request.reference}", + """ + Your support request #{request.reference} was updated. + + Status: #{request.status} + Response: + #{request.resolution_note || "No additional response was provided."} + + View the current status: + #{status_url} + """ + ) + end + + def deliver_operator_alert(%SupportRequest{} = request) do + case Application.get_env(:who_need_help, :support_inbox_address) do + address when is_binary(address) and address != "" -> + deliver( + address, + "New support request #{request.reference}", + """ + A new #{request.kind} support request is waiting for review. + + Reference: #{request.reference} + Operator queue: #{WhoNeedHelpWeb.Endpoint.url()}/support/operations + + Sign in with an authorised moderator or administrator account to review it. + """ + ) + + _other -> + {:ok, :not_configured} + end + end + + defp deliver(recipient, subject, body) do + from = Application.fetch_env!(:who_need_help, :mailer_from) + + new() + |> to(recipient) + |> from({from[:name], from[:address]}) + |> maybe_reply_to() + |> subject(subject) + |> text_body(body) + |> Mailer.deliver() + end + + defp maybe_reply_to(email) do + case Application.get_env(:who_need_help, :support_inbox_address) do + address when is_binary(address) and address != "" -> reply_to(email, address) + _other -> email + end + end +end diff --git a/lib/who_need_help/support/support_request.ex b/lib/who_need_help/support/support_request.ex new file mode 100644 index 0000000..7047d5a --- /dev/null +++ b/lib/who_need_help/support/support_request.ex @@ -0,0 +1,61 @@ +defmodule WhoNeedHelp.Support.SupportRequest do + use Ecto.Schema + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + + schema "support_requests" do + field :reference, :string + + field :kind, Ecto.Enum, + values: [ + :account_access, + :technical_issue, + :safety_concern, + :moderation_appeal, + :account_deletion, + :data_export, + :privacy_request, + :other + ] + + field :status, Ecto.Enum, + values: [:open, :reviewing, :waiting_for_requester, :resolved, :closed], + default: :open + + field :contact_email, :string + field :subject, :string + field :details, :string + field :contact_verified_at, :utc_datetime + field :resolution_note, :string + field :reviewed_at, :utc_datetime + field :response_sent_at, :utc_datetime + belongs_to :requester, WhoNeedHelp.Accounts.User + belongs_to :reviewed_by, WhoNeedHelp.Accounts.User + timestamps(type: :utc_datetime) + end + + def submission_changeset(request, attrs) do + request + |> cast(attrs, [:kind, :contact_email, :subject, :details]) + |> validate_required([:kind, :contact_email, :subject, :details, :reference]) + |> update_change(:contact_email, &normalize_email/1) + |> validate_format(:contact_email, ~r/^[^@,;\s]+@[^@,;\s]+$/, + message: "must have the @ sign and no spaces" + ) + |> validate_length(:contact_email, max: 160) + |> validate_length(:subject, min: 3, max: 160) + |> validate_length(:details, min: 10, max: 5_000) + |> unique_constraint(:reference) + end + + def moderation_changeset(request, attrs) do + request + |> cast(attrs, [:status, :resolution_note, :reviewed_at, :reviewed_by_id, :response_sent_at]) + |> validate_required([:status, :reviewed_at, :reviewed_by_id]) + |> validate_length(:resolution_note, max: 5_000) + end + + defp normalize_email(email), do: email |> String.trim() |> String.downcase() +end diff --git a/lib/who_need_help_web/components/layouts.ex b/lib/who_need_help_web/components/layouts.ex index 0a2aa47..d661a7b 100644 --- a/lib/who_need_help_web/components/layouts.ex +++ b/lib/who_need_help_web/components/layouts.ex @@ -144,6 +144,10 @@ defmodule WhoNeedHelpWeb.Layouts do
<.link href={~p"/safety"} class="link">{gettext("Safety rules")} · + <.link href={~p"/support"} class="link">{gettext("Support")} + · + <.link href={~p"/legal/content-removal"} class="link">{gettext("Report content")} + · <.link href={~p"/feedback"} class="link">{gettext("Build Week feedback")}
@@ -181,6 +185,11 @@ defmodule WhoNeedHelpWeb.Layouts do {gettext("Moderation")} +
  • + <.link navigate={~p"/support/operations"} class={["btn-warning", @link_class]}> + {gettext("Support queue")} + +
  • """ end diff --git a/lib/who_need_help_web/controllers/content_removal_controller.ex b/lib/who_need_help_web/controllers/content_removal_controller.ex new file mode 100644 index 0000000..dc2c1a0 --- /dev/null +++ b/lib/who_need_help_web/controllers/content_removal_controller.ex @@ -0,0 +1,103 @@ +defmodule WhoNeedHelpWeb.ContentRemovalController do + use WhoNeedHelpWeb, :controller + + alias WhoNeedHelp.ContentRemoval + alias WhoNeedHelp.ContentRemoval.Notice + + def new(conn, params) do + attrs = + case internal_location(params["location"]) do + nil -> %{} + location -> %{"content_locations" => location} + end + + render_form(conn, :general, %Notice{}, attrs) + end + + def take_it_down(conn, _params), do: render_form(conn, :take_it_down, %Notice{}, %{}) + + def create(conn, %{"notice" => params}) when is_map(params), + do: create_notice(conn, :general, params) + + def create(conn, _params), do: send_resp(conn, :bad_request, "Bad Request") + + def create_take_it_down(conn, %{"notice" => params}) when is_map(params), + do: create_notice(conn, :take_it_down, params) + + def create_take_it_down(conn, _params), + do: send_resp(conn, :bad_request, "Bad Request") + + def show(conn, %{"id" => id, "token" => token}) do + case ContentRemoval.get_by_access_token(id, token) do + {:ok, notice} -> render(conn, :show, notice: notice) + {:error, :not_found} -> send_resp(conn, :not_found, "Not found") + end + end + + def show(conn, _params), do: send_resp(conn, :not_found, "Not found") + + defp create_notice(conn, regime, params) do + case ContentRemoval.create_notice(conn.assigns.current_scope, regime, params) do + {:ok, notice} -> + redirect(conn, + to: ~p"/legal/content-removal/received?reference=#{notice.reference}" + ) + + {:error, %Ecto.Changeset{} = changeset} -> + conn + |> put_status(:unprocessable_entity) + |> render_form(regime, changeset, params) + + {:error, :rate_limited} -> + conn + |> put_status(:too_many_requests) + |> put_flash(:error, gettext("Too many removal notices. Please try again later.")) + |> render_form(regime, %Notice{}, params) + end + end + + def received(conn, %{"reference" => reference}) when is_binary(reference) do + render(conn, :received, reference: String.slice(reference, 0, 64)) + end + + def received(conn, _params), do: redirect(conn, to: ~p"/legal/content-removal") + + defp render_form(conn, regime, %Ecto.Changeset{} = changeset, _attrs) do + render(conn, form_template(regime), + form: Phoenix.Component.to_form(changeset, as: :notice), + contact_email: current_email(conn) + ) + end + + defp render_form(conn, regime, %Notice{} = notice, attrs) do + attrs = with_contact_email(conn, attrs) + notice = %{notice | regime: regime} + changeset = ContentRemoval.change_notice(notice, attrs) + render_form(conn, regime, changeset, attrs) + end + + defp form_template(:take_it_down), do: :take_it_down + defp form_template(_regime), do: :new + + defp with_contact_email(conn, attrs) do + case current_email(conn) do + nil -> attrs + email -> Map.put(attrs, "contact_email", email) + end + end + + defp current_email(%{assigns: %{current_scope: %{user: %{email: email}}}}), do: email + defp current_email(_conn), do: nil + + defp internal_location(value) when is_binary(value) do + uri = URI.parse(value) + + if uri.scheme == nil and uri.host == nil and is_binary(uri.path) and + String.starts_with?(uri.path, "/") and not String.starts_with?(uri.path, "//") and + not String.contains?(value, ["\r", "\n"]) do + WhoNeedHelpWeb.Endpoint.url() <> value + end + end + + defp internal_location(_value), do: nil +end diff --git a/lib/who_need_help_web/controllers/content_removal_html.ex b/lib/who_need_help_web/controllers/content_removal_html.ex new file mode 100644 index 0000000..4e48374 --- /dev/null +++ b/lib/who_need_help_web/controllers/content_removal_html.ex @@ -0,0 +1,29 @@ +defmodule WhoNeedHelpWeb.ContentRemovalHTML do + use WhoNeedHelpWeb, :html + + embed_templates "content_removal_html/*" + + def category_label(:illegal_content), do: gettext("Illegal content") + def category_label(:privacy_violation), do: gettext("Privacy violation") + def category_label(:copyright), do: gettext("Copyright") + def category_label(:impersonation), do: gettext("Impersonation") + + def category_label(:non_consensual_intimate_media), + do: gettext("Non-consensual intimate image or video") + + def category_label(:child_sexual_abuse_material), + do: gettext("Sexual material involving a minor") + + def category_label(:threat_to_life_or_safety), do: gettext("Threat to life or safety") + def category_label(:other), do: gettext("Other") + def category_label(value), do: to_string(value) + + def status_label(:open), do: gettext("Open") + def status_label(:urgent_review), do: gettext("Urgent review") + def status_label(:reviewing), do: gettext("Reviewing") + def status_label(:needs_information), do: gettext("More information needed") + def status_label(:actioned), do: gettext("Action taken") + def status_label(:rejected), do: gettext("Rejected") + def status_label(:closed), do: gettext("Closed") + def status_label(value), do: to_string(value) +end diff --git a/lib/who_need_help_web/controllers/content_removal_html/new.html.heex b/lib/who_need_help_web/controllers/content_removal_html/new.html.heex new file mode 100644 index 0000000..528e2b7 --- /dev/null +++ b/lib/who_need_help_web/controllers/content_removal_html/new.html.heex @@ -0,0 +1,100 @@ + +
    +
    {gettext("CONTENT REMOVAL")}
    +

    {gettext("Report illegal or harmful content")}

    +

    + {gettext( + "Use this electronic notice for specific content on Who Need Help that you believe is illegal or infringes your rights. Ordinary safety reports inside a request or chat remain available to signed-in users." + )} +

    +
    + {gettext( + "Provide exact Who Need Help URLs. Do not upload or paste intimate imagery, identity documents, passwords, access codes, or unnecessary medical information." + )} +
    + + <.form for={@form} action={~p"/legal/content-removal"} class="mt-8 space-y-4"> + <.input + field={@form[:category]} + type="select" + label={gettext("Reason")} + options={[ + {gettext("Illegal content"), "illegal_content"}, + {gettext("Privacy violation"), "privacy_violation"}, + {gettext("Copyright"), "copyright"}, + {gettext("Impersonation"), "impersonation"}, + {gettext("Non-consensual intimate image or video"), "non_consensual_intimate_media"}, + {gettext("Sexual material involving a minor"), "child_sexual_abuse_material"}, + {gettext("Threat to life or safety"), "threat_to_life_or_safety"}, + {gettext("Other"), "other"} + ]} + prompt={gettext("Choose a reason")} + required + /> + <.input field={@form[:submitter_name]} label={gettext("Your name or organisation")} /> + <.input + field={@form[:contact_email]} + type="email" + label={gettext("Contact email")} + value={@contact_email || @form[:contact_email].value} + readonly={not is_nil(@contact_email)} + /> + <.input + field={@form[:relationship]} + type="select" + label={gettext("Your relationship to the affected person or rights holder")} + options={[ + {gettext("I am the affected person or rights holder"), "self"}, + {gettext("I am an authorised representative"), "authorized_representative"}, + {gettext("Other"), "other"} + ]} + prompt={gettext("Choose a relationship")} + /> + <.input + field={@form[:content_locations]} + type="textarea" + label={gettext("Exact content URLs — one per line")} + required + /> + <.input + field={@form[:explanation]} + type="textarea" + label={gettext("Why do you believe this content should be removed?")} + required + /> + <.input + field={@form[:legal_basis]} + type="textarea" + label={gettext("Law, right, or policy involved, if known")} + /> + <.input + field={@form[:electronic_signature]} + label={gettext("Electronic signature (type your full name)")} + /> + <.input + field={@form[:good_faith]} + type="checkbox" + label={ + gettext( + "I believe in good faith that the identified content is illegal or infringes the stated right." + ) + } + /> + <.input + field={@form[:accurate_complete]} + type="checkbox" + label={ + gettext( + "I confirm that this notice is accurate and complete to the best of my knowledge." + ) + } + /> +

    + {gettext( + "Identity and contact fields may be omitted for reports of sexual material involving a minor. Do not include or upload the material itself." + )} +

    + <.button class="btn btn-error w-full">{gettext("Submit removal notice")} + +
    +
    diff --git a/lib/who_need_help_web/controllers/content_removal_html/received.html.heex b/lib/who_need_help_web/controllers/content_removal_html/received.html.heex new file mode 100644 index 0000000..e65923e --- /dev/null +++ b/lib/who_need_help_web/controllers/content_removal_html/received.html.heex @@ -0,0 +1,16 @@ + +
    +
    {gettext("NOTICE RECEIVED")}
    +

    + {gettext("Your reference is %{reference}", reference: @reference)} +

    +

    + {gettext( + "A private status link has been sent when a contact email was provided and delivery is configured. Opening that link verifies the contact address. Keep the reference for follow-up." + )} +

    + <.link navigate={~p"/legal/content-removal"} class="btn btn-primary mt-7"> + {gettext("Back to removal form")} + +
    +
    diff --git a/lib/who_need_help_web/controllers/content_removal_html/show.html.heex b/lib/who_need_help_web/controllers/content_removal_html/show.html.heex new file mode 100644 index 0000000..63ad3bf --- /dev/null +++ b/lib/who_need_help_web/controllers/content_removal_html/show.html.heex @@ -0,0 +1,39 @@ + +
    +
    + {@notice.reference} + + {status_label(@notice.status)} + +
    +

    {gettext("Content-removal notice")}

    +

    {category_label(@notice.category)}

    +
    +

    {gettext("Reported locations")}

    +
      +
    • + {url} +
    • +
    +
    +
    +

    {gettext("Decision or request for information")}

    +

    {@notice.resolution_note}

    +
    +

    + {gettext( + "Keep this status link private. The submitter identity is not shown publicly or automatically disclosed to the person who posted the reported content." + )} +

    +
    +
    diff --git a/lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex b/lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex new file mode 100644 index 0000000..f05058f --- /dev/null +++ b/lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex @@ -0,0 +1,92 @@ + +
    +
    {gettext("TAKE IT DOWN")}
    +

    + {gettext("Request removal of intimate visual material")} +

    +

    + {gettext( + "Use this dedicated process when an identifiable person is shown in an intimate image or video shared without consent, including a digital forgery. You may submit for yourself or as an authorised representative." + )} +

    +
    + {gettext( + "Do not upload the image or video. Provide only the exact Who Need Help URL where it appears and enough text to identify it safely." + )} +
    + + <.form for={@form} action={~p"/legal/take-it-down"} class="mt-8 space-y-4"> + <.input + field={@form[:category]} + type="select" + label={gettext("Material involved")} + options={[ + {gettext("Non-consensual intimate image or video"), "non_consensual_intimate_media"}, + {gettext("Sexual material involving a minor"), "child_sexual_abuse_material"} + ]} + prompt={gettext("Choose a category")} + required + /> + <.input field={@form[:submitter_name]} label={gettext("Your full name")} required /> + <.input + field={@form[:contact_email]} + type="email" + label={gettext("Contact email")} + value={@contact_email || @form[:contact_email].value} + readonly={not is_nil(@contact_email)} + required + /> + <.input + field={@form[:relationship]} + type="select" + label={gettext("Who are you submitting for?")} + options={[ + {gettext("Myself"), "self"}, + {gettext("A person I am authorised to represent"), "authorized_representative"} + ]} + prompt={gettext("Choose a relationship")} + required + /> + <.input + field={@form[:content_locations]} + type="textarea" + label={gettext("Exact content URLs — one per line")} + required + /> + <.input + field={@form[:explanation]} + type="textarea" + label={ + gettext( + "Identify the material without reproducing it and explain why it is non-consensual" + ) + } + required + /> + <.input + field={@form[:electronic_signature]} + label={gettext("Electronic signature (type your full name)")} + required + /> + <.input + field={@form[:good_faith]} + type="checkbox" + label={ + gettext( + "I have a good-faith belief that this intimate visual material was shared without consent." + ) + } + /> + <.input + field={@form[:accurate_complete]} + type="checkbox" + label={ + gettext( + "I confirm that the information in this request is accurate and that I am the depicted person or authorised to act for them." + ) + } + /> + <.button class="btn btn-error w-full">{gettext("Submit urgent removal request")} + +
    +
    diff --git a/lib/who_need_help_web/controllers/support_controller.ex b/lib/who_need_help_web/controllers/support_controller.ex new file mode 100644 index 0000000..6a4a5ea --- /dev/null +++ b/lib/who_need_help_web/controllers/support_controller.ex @@ -0,0 +1,128 @@ +defmodule WhoNeedHelpWeb.SupportController do + use WhoNeedHelpWeb, :controller + + alias WhoNeedHelp.Support + alias WhoNeedHelp.Support.SupportRequest + + def index(conn, _params) do + requests = + case conn.assigns.current_scope do + nil -> [] + scope -> Support.list_for_user(scope) + end + + render(conn, :index, requests: requests) + end + + def new(conn, _params) do + render_form(conn, %SupportRequest{}, %{}, :new) + end + + def create(conn, %{"support_request" => params}) when is_map(params) do + case Support.create_request(conn.assigns.current_scope, params) do + {:ok, request} -> + redirect(conn, to: ~p"/support/received?reference=#{request.reference}") + + {:error, %Ecto.Changeset{} = changeset} -> + conn + |> put_status(:unprocessable_entity) + |> render_form(changeset, %{}, :new) + + {:error, :rate_limited} -> + conn + |> put_status(:too_many_requests) + |> put_flash(:error, gettext("Too many support requests. Please try again later.")) + |> render_form(%SupportRequest{}, params, :new) + end + end + + def create(conn, _params), do: send_resp(conn, :bad_request, "Bad Request") + + def delete_account(conn, _params) do + render_form( + conn, + %SupportRequest{}, + %{ + "kind" => "account_deletion", + "subject" => gettext("Delete my Who Need Help account"), + "details" => gettext("Please delete my account and associated personal data.") + }, + :delete_account + ) + end + + def request_account_deletion(conn, %{"support_request" => params}) when is_map(params) do + params = + params + |> Map.put("kind", "account_deletion") + |> Map.put("subject", gettext("Delete my Who Need Help account")) + + case Support.create_request(conn.assigns.current_scope, params) do + {:ok, request} -> + redirect(conn, to: ~p"/support/received?reference=#{request.reference}") + + {:error, %Ecto.Changeset{} = changeset} -> + conn + |> put_status(:unprocessable_entity) + |> render_form(changeset, params, :delete_account) + + {:error, :rate_limited} -> + conn + |> put_status(:too_many_requests) + |> put_flash(:error, gettext("Too many support requests. Please try again later.")) + |> render_form(%SupportRequest{}, params, :delete_account) + end + end + + def request_account_deletion(conn, _params), + do: send_resp(conn, :bad_request, "Bad Request") + + def show(conn, %{"id" => id} = params) do + token = params["token"] + + result = + case conn.assigns.current_scope do + nil -> + Support.get_by_access_token(id, token) + + scope -> + case Support.get_for_user(scope, id) do + {:ok, request} -> {:ok, request} + _ -> Support.get_by_access_token(id, token) + end + end + + case result do + {:ok, request} -> render(conn, :show, request: request) + {:error, :not_found} -> send_resp(conn, :not_found, "Not found") + end + end + + def received(conn, %{"reference" => reference}) when is_binary(reference) do + render(conn, :received, reference: String.slice(reference, 0, 64)) + end + + def received(conn, _params), do: redirect(conn, to: ~p"/support") + + defp render_form(conn, %Ecto.Changeset{} = changeset, _attrs, page) do + render(conn, page, + form: Phoenix.Component.to_form(changeset, as: :support_request), + contact_email: current_email(conn) + ) + end + + defp render_form(conn, %SupportRequest{} = request, attrs, page) do + changeset = Support.change_request(request, with_contact_email(conn, attrs)) + render_form(conn, changeset, attrs, page) + end + + defp with_contact_email(conn, attrs) do + case current_email(conn) do + nil -> attrs + email -> Map.put(attrs, "contact_email", email) + end + end + + defp current_email(%{assigns: %{current_scope: %{user: %{email: email}}}}), do: email + defp current_email(_conn), do: nil +end diff --git a/lib/who_need_help_web/controllers/support_html.ex b/lib/who_need_help_web/controllers/support_html.ex new file mode 100644 index 0000000..cababfd --- /dev/null +++ b/lib/who_need_help_web/controllers/support_html.ex @@ -0,0 +1,22 @@ +defmodule WhoNeedHelpWeb.SupportHTML do + use WhoNeedHelpWeb, :html + + embed_templates "support_html/*" + + def kind_label(:account_access), do: gettext("Account access") + def kind_label(:technical_issue), do: gettext("Technical issue") + def kind_label(:safety_concern), do: gettext("Safety concern") + def kind_label(:moderation_appeal), do: gettext("Moderation appeal") + def kind_label(:account_deletion), do: gettext("Account deletion") + def kind_label(:data_export), do: gettext("Data export") + def kind_label(:privacy_request), do: gettext("Privacy request") + def kind_label(:other), do: gettext("Other") + def kind_label(value), do: to_string(value) + + def status_label(:open), do: gettext("Open") + def status_label(:reviewing), do: gettext("Reviewing") + def status_label(:waiting_for_requester), do: gettext("Waiting for your response") + def status_label(:resolved), do: gettext("Resolved") + def status_label(:closed), do: gettext("Closed") + def status_label(value), do: to_string(value) +end diff --git a/lib/who_need_help_web/controllers/support_html/delete_account.html.heex b/lib/who_need_help_web/controllers/support_html/delete_account.html.heex new file mode 100644 index 0000000..58db3d8 --- /dev/null +++ b/lib/who_need_help_web/controllers/support_html/delete_account.html.heex @@ -0,0 +1,40 @@ + +
    +
    {gettext("ACCOUNT AND DATA")}
    +

    {gettext("Request account deletion")}

    +

    + {gettext( + "This requests deletion of your Who Need Help account and associated personal data. Some records may need to be retained when required for safety, fraud prevention, legal obligations, or resolving an active dispute; any retained data must be described in the applicable retention notice." + )} +

    +
    + {gettext( + "Submitting this form does not immediately sign you out or erase evidence required for an open safety investigation. Support will verify and process the request." + )} +
    + + <.form for={@form} action={~p"/account/delete"} class="mt-8 space-y-4"> + + + <.input + field={@form[:contact_email]} + type="email" + label={gettext("Account email")} + value={@contact_email || @form[:contact_email].value} + readonly={not is_nil(@contact_email)} + required + /> + <.input + field={@form[:details]} + type="textarea" + label={gettext("Additional information")} + required + /> + <.button class="btn btn-error w-full">{gettext("Request account deletion")} + +
    +
    diff --git a/lib/who_need_help_web/controllers/support_html/index.html.heex b/lib/who_need_help_web/controllers/support_html/index.html.heex new file mode 100644 index 0000000..de6f9c1 --- /dev/null +++ b/lib/who_need_help_web/controllers/support_html/index.html.heex @@ -0,0 +1,30 @@ + +
    +
    +
    +
    {gettext("SUPPORT")}
    +

    {gettext("Your requests")}

    +
    + <.link navigate={~p"/support/new"} class="btn btn-primary">{gettext("New request")} +
    + +
    + <.link + :for={request <- @requests} + navigate={~p"/support/cases/#{request.id}"} + class="block rounded-2xl border border-base-300 p-5 transition hover:border-primary" + > +
    + {request.reference} + {status_label(request.status)} + {kind_label(request.kind)} +
    +

    {request.subject}

    + + +

    + {gettext("You have not submitted any support requests.")} +

    +
    +
    +
    diff --git a/lib/who_need_help_web/controllers/support_html/new.html.heex b/lib/who_need_help_web/controllers/support_html/new.html.heex new file mode 100644 index 0000000..93ec2ab --- /dev/null +++ b/lib/who_need_help_web/controllers/support_html/new.html.heex @@ -0,0 +1,63 @@ + +
    +
    {gettext("SUPPORT")}
    +

    {gettext("Contact Who Need Help")}

    +

    + {gettext( + "Use this form for account access, technical problems, privacy questions, or other support. For illegal content or a removal request, use the dedicated removal form." + )} +

    + +
    + <.link navigate={~p"/legal/content-removal"} class="btn btn-outline btn-sm"> + {gettext("Report content")} + + <.link navigate={~p"/legal/take-it-down"} class="btn btn-outline btn-sm"> + {gettext("TAKE IT DOWN request")} + + <.link navigate={~p"/account/delete"} class="btn btn-outline btn-sm"> + {gettext("Delete an account")} + +
    + + <.form for={@form} action={~p"/support"} class="mt-8 space-y-4"> + <.input + field={@form[:kind]} + type="select" + label={gettext("What do you need help with?")} + options={[ + {gettext("Account access"), "account_access"}, + {gettext("Technical issue"), "technical_issue"}, + {gettext("Safety concern"), "safety_concern"}, + {gettext("Appeal a moderation decision"), "moderation_appeal"}, + {gettext("Privacy request"), "privacy_request"}, + {gettext("Data export"), "data_export"}, + {gettext("Other"), "other"} + ]} + prompt={gettext("Choose a topic")} + required + /> + <.input + field={@form[:contact_email]} + type="email" + label={gettext("Contact email")} + value={@contact_email || @form[:contact_email].value} + readonly={not is_nil(@contact_email)} + required + /> + <.input field={@form[:subject]} label={gettext("Subject")} required /> + <.input + field={@form[:details]} + type="textarea" + label={gettext("Describe the problem")} + required + /> +

    + {gettext( + "Do not include passwords, payment card details, access codes, prescriptions, or unnecessary medical information." + )} +

    + <.button class="btn btn-primary w-full">{gettext("Send support request")} + +
    +
    diff --git a/lib/who_need_help_web/controllers/support_html/received.html.heex b/lib/who_need_help_web/controllers/support_html/received.html.heex new file mode 100644 index 0000000..a6f3b22 --- /dev/null +++ b/lib/who_need_help_web/controllers/support_html/received.html.heex @@ -0,0 +1,13 @@ + +
    +
    {gettext("REQUEST RECEIVED")}
    +

    {gettext("Check your email")}

    +

    + {gettext( + "We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests.", + reference: @reference + )} +

    + <.link navigate={~p"/support"} class="btn btn-primary mt-7">{gettext("Back to support")} +
    +
    diff --git a/lib/who_need_help_web/controllers/support_html/show.html.heex b/lib/who_need_help_web/controllers/support_html/show.html.heex new file mode 100644 index 0000000..46dbf43 --- /dev/null +++ b/lib/who_need_help_web/controllers/support_html/show.html.heex @@ -0,0 +1,25 @@ + +
    +
    + {@request.reference} + {status_label(@request.status)} +
    +

    {@request.subject}

    +

    {kind_label(@request.kind)}

    +
    +

    {@request.details}

    +
    +
    +

    {gettext("Support response")}

    +

    {@request.resolution_note}

    +
    +

    + {gettext( + "Keep this status link private. A copy is sent to the contact email when email delivery is configured." + )} +

    +
    +
    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 849e466..cbebfcf 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 @@ -108,4 +108,23 @@ {gettext("Save Password")} + +
    + +
    +

    {gettext("Account and personal data")}

    +

    + {gettext( + "Request deletion of your account and associated personal data, or contact support about access and export requests." + )} +

    +
    + <.link navigate={~p"/account/delete"} class="btn btn-error btn-outline"> + {gettext("Request account deletion")} + + <.link navigate={~p"/support/requests"} class="btn btn-outline"> + {gettext("View support requests")} + +
    +
    diff --git a/lib/who_need_help_web/live/activity_live/show.ex b/lib/who_need_help_web/live/activity_live/show.ex index e1e7182..c897765 100644 --- a/lib/who_need_help_web/live/activity_live/show.ex +++ b/lib/who_need_help_web/live/activity_live/show.ex @@ -689,6 +689,12 @@ defmodule WhoNeedHelpWeb.ActivityLive.Show do + <.link + navigate={~p"/legal/content-removal?location=/activities/#{@activity.id}"} + class="btn btn-ghost btn-sm w-full" + > + {gettext("Submit a legal content-removal notice")} +
    diff --git a/lib/who_need_help_web/live/request_live/show.ex b/lib/who_need_help_web/live/request_live/show.ex index 2c56e68..ef5667e 100644 --- a/lib/who_need_help_web/live/request_live/show.ex +++ b/lib/who_need_help_web/live/request_live/show.ex @@ -1120,6 +1120,12 @@ defmodule WhoNeedHelpWeb.RequestLive.Show do + <.link + navigate={~p"/legal/content-removal?location=/requests/#{@request.id}"} + class="btn btn-ghost btn-sm w-full" + > + {gettext("Submit a legal content-removal notice")} + diff --git a/lib/who_need_help_web/live/support_operations_live.ex b/lib/who_need_help_web/live/support_operations_live.ex new file mode 100644 index 0000000..512f138 --- /dev/null +++ b/lib/who_need_help_web/live/support_operations_live.ex @@ -0,0 +1,282 @@ +defmodule WhoNeedHelpWeb.SupportOperationsLive do + use WhoNeedHelpWeb, :live_view + + alias WhoNeedHelp.{ContentRemoval, Support} + + @impl true + def mount(_params, _session, socket) do + {:ok, socket |> assign(:page_title, gettext("Support operations")) |> load()} + end + + @impl true + def handle_event("moderate-support", %{"id" => id, "moderation" => params}, socket) do + respond( + socket, + Support.moderate(socket.assigns.current_scope, id, params), + gettext("Support request updated.") + ) + end + + def handle_event("moderate-removal", %{"id" => id, "moderation" => params}, socket) do + respond( + socket, + ContentRemoval.moderate(socket.assigns.current_scope, id, params), + gettext("Removal notice updated.") + ) + end + + def handle_event("load-more-support", _params, socket) do + page = + Support.paginate_for_staff(socket.assigns.current_scope, + after: socket.assigns.support_cursor + ) + + {:noreply, append_page(socket, :support_requests, :support_cursor, page)} + end + + def handle_event("load-more-removals", _params, socket) do + page = + ContentRemoval.paginate_for_staff(socket.assigns.current_scope, + after: socket.assigns.removal_cursor + ) + + {:noreply, append_page(socket, :removal_notices, :removal_cursor, page)} + end + + defp respond(socket, {:ok, _record}, message) do + {:noreply, socket |> put_flash(:info, message) |> load()} + end + + defp respond(socket, {:error, reason}, _message) do + {:noreply, put_flash(socket, :error, error_message(reason))} + end + + defp load(socket) do + support = Support.paginate_for_staff(socket.assigns.current_scope) + removals = ContentRemoval.paginate_for_staff(socket.assigns.current_scope) + + socket + |> assign(:support_requests, support.entries) + |> assign(:support_cursor, support.next_cursor) + |> assign(:removal_notices, removals.entries) + |> assign(:removal_cursor, removals.next_cursor) + end + + defp append_page(socket, entries_key, cursor_key, page) do + existing = socket.assigns[entries_key] + existing_ids = MapSet.new(existing, & &1.id) + entries = existing ++ Enum.reject(page.entries, &MapSet.member?(existing_ids, &1.id)) + + socket |> assign(entries_key, entries) |> assign(cursor_key, page.next_cursor) + end + + defp error_message(:forbidden), do: gettext("Moderator access is required.") + defp error_message(:not_found), do: gettext("The selected record is no longer available.") + defp error_message(%Ecto.Changeset{}), do: gettext("Please check the submitted fields.") + defp error_message(_reason), do: gettext("Could not update the request. Please try again.") + + defp support_kind(:account_access), do: gettext("Account access") + defp support_kind(:technical_issue), do: gettext("Technical issue") + defp support_kind(:safety_concern), do: gettext("Safety concern") + defp support_kind(:moderation_appeal), do: gettext("Moderation appeal") + defp support_kind(:account_deletion), do: gettext("Account deletion") + defp support_kind(:data_export), do: gettext("Data export") + defp support_kind(:privacy_request), do: gettext("Privacy request") + defp support_kind(:other), do: gettext("Other") + defp support_kind(value), do: to_string(value) + + defp removal_category(:illegal_content), do: gettext("Illegal content") + defp removal_category(:privacy_violation), do: gettext("Privacy violation") + defp removal_category(:copyright), do: gettext("Copyright") + defp removal_category(:impersonation), do: gettext("Impersonation") + + defp removal_category(:non_consensual_intimate_media), + do: gettext("Non-consensual intimate image or video") + + defp removal_category(:child_sexual_abuse_material), + do: gettext("Sexual material involving a minor") + + defp removal_category(:threat_to_life_or_safety), do: gettext("Threat to life or safety") + defp removal_category(:other), do: gettext("Other") + defp removal_category(value), do: to_string(value) + + defp status_label(value), do: value |> to_string() |> String.replace("_", " ") + + defp scoped_form(data, id), do: to_form(data, as: :moderation, id: id) + + @impl true + def render(assigns) do + ~H""" + +
    {gettext("RESTRICTED WORKSPACE")}
    +
    +
    +

    {gettext("Support operations")}

    +

    + {gettext( + "Support requests and legal removal notices are separate queues. Decisions and operator identity are audited." + )} +

    +
    + <.link navigate={~p"/moderation"} class="btn btn-outline btn-sm"> + {gettext("Trust and safety moderation")} + +
    + +
    +

    {gettext("Content removal and TAKE IT DOWN")}

    +
    +
    + <% form = + scoped_form( + %{ + "status" => to_string(notice.status), + "resolution_note" => notice.resolution_note || "" + }, + "removal-#{notice.id}" + ) %> +
    + {notice.reference} + {notice.regime} + {status_label(notice.status)} + + {gettext("contact unverified")} + + {removal_category(notice.category)} + + {gettext("review by %{time}", time: notice.response_due_at)} + +
    +
    +
    {gettext("Submitter")}
    +
    {notice.submitter_name || gettext("Not provided")}
    +
    {gettext("Contact")}
    +
    {notice.contact_email || gettext("Not provided")}
    +
    {gettext("Locations")}
    +
    +
    + {url} +
    +
    +
    +

    {notice.explanation}

    +

    + {gettext("Stated basis:")} {notice.legal_basis} +

    + <.form + for={form} + phx-submit="moderate-removal" + phx-value-id={notice.id} + class="mt-4 grid gap-2 md:grid-cols-[13rem_1fr_auto]" + > + <.input + field={form[:status]} + type="select" + options={[ + {gettext("Open"), "open"}, + {gettext("Urgent review"), "urgent_review"}, + {gettext("Reviewing"), "reviewing"}, + {gettext("More information needed"), "needs_information"}, + {gettext("Action taken"), "actioned"}, + {gettext("Rejected"), "rejected"}, + {gettext("Closed"), "closed"} + ]} + /> + <.input + field={form[:resolution_note]} + type="textarea" + placeholder={gettext("Decision or information request")} + /> + <.button class="btn btn-error self-end">{gettext("Save and notify")} + +
    +

    + {gettext("No removal notices.")} +

    +
    + +
    + +
    +

    {gettext("General support and data requests")}

    +
    +
    + <% form = + scoped_form( + %{ + "status" => to_string(request.status), + "resolution_note" => request.resolution_note || "" + }, + "support-#{request.id}" + ) %> +
    + {request.reference} + {status_label(request.status)} + + {gettext("contact unverified")} + + {support_kind(request.kind)} +
    +

    {request.subject}

    +

    {request.contact_email}

    +

    {request.details}

    + <.form + for={form} + phx-submit="moderate-support" + phx-value-id={request.id} + class="mt-4 grid gap-2 md:grid-cols-[13rem_1fr_auto]" + > + <.input + field={form[:status]} + type="select" + options={[ + {gettext("Open"), "open"}, + {gettext("Reviewing"), "reviewing"}, + {gettext("Waiting for requester"), "waiting_for_requester"}, + {gettext("Resolved"), "resolved"}, + {gettext("Closed"), "closed"} + ]} + /> + <.input + field={form[:resolution_note]} + type="textarea" + placeholder={gettext("Support response")} + /> + <.button class="btn btn-primary self-end">{gettext("Save and notify")} + +
    +

    + {gettext("No support requests.")} +

    +
    + +
    +
    + """ + end +end diff --git a/lib/who_need_help_web/router.ex b/lib/who_need_help_web/router.ex index f911756..4e63f3b 100644 --- a/lib/who_need_help_web/router.ex +++ b/lib/who_need_help_web/router.ex @@ -72,6 +72,19 @@ defmodule WhoNeedHelpWeb.Router do get "/", PageController, :home get "/feedback", FeedbackController, :show get "/safety", PageController, :safety + get "/support", SupportController, :new + get "/support/new", SupportController, :new + post "/support", SupportController, :create + get "/support/received", SupportController, :received + get "/support/cases/:id", SupportController, :show + get "/account/delete", SupportController, :delete_account + post "/account/delete", SupportController, :request_account_deletion + get "/legal/content-removal", ContentRemovalController, :new + post "/legal/content-removal", ContentRemovalController, :create + get "/legal/content-removal/received", ContentRemovalController, :received + get "/legal/content-removal/:id", ContentRemovalController, :show + get "/legal/take-it-down", ContentRemovalController, :take_it_down + post "/legal/take-it-down", ContentRemovalController, :create_take_it_down end # Other scopes may use custom stacks. @@ -118,6 +131,7 @@ defmodule WhoNeedHelpWeb.Router do delete "/auth/google/link", GoogleAuthController, :disconnect get "/auth/social/:provider", SocialOAuthController, :request get "/auth/social/:provider/callback", SocialOAuthController, :callback + get "/support/requests", SupportController, :index end scope "/", WhoNeedHelpWeb do @@ -139,6 +153,7 @@ defmodule WhoNeedHelpWeb.Router do live_session :moderation, on_mount: [{WhoNeedHelpWeb.UserAuth, :ensure_moderator}] do live "/moderation", ModerationLive, :index + live "/support/operations", SupportOperationsLive, :index end end diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 0c4b56a..5120383 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -12,7 +12,7 @@ msgid "" msgstr "" #: lib/who_need_help_web/components/layouts.ex:111 -#: lib/who_need_help_web/components/layouts.ex:257 +#: lib/who_need_help_web/components/layouts.ex:266 #, elixir-autogen, elixir-format msgid "Account settings" msgstr "" @@ -30,13 +30,13 @@ msgstr "" msgid "Ask for help" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:295 -#: lib/who_need_help_web/components/layouts.ex:310 +#: lib/who_need_help_web/components/layouts.ex:304 +#: lib/who_need_help_web/components/layouts.ex:319 #, elixir-autogen, elixir-format msgid "Attempting to reconnect" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:171 +#: lib/who_need_help_web/components/layouts.ex:175 #, elixir-autogen, elixir-format msgid "Categories" msgstr "" @@ -65,13 +65,13 @@ msgid "Log in" msgstr "" #: lib/who_need_help_web/components/layouts.ex:114 -#: lib/who_need_help_web/components/layouts.ex:260 +#: lib/who_need_help_web/components/layouts.ex:269 #, elixir-autogen, elixir-format msgid "Log out" msgstr "" #: lib/who_need_help_web/components/layouts.ex:108 -#: lib/who_need_help_web/components/layouts.ex:254 +#: lib/who_need_help_web/components/layouts.ex:263 #: lib/who_need_help_web/live/profile_live.ex:16 #, elixir-autogen, elixir-format msgid "Profile" @@ -83,7 +83,7 @@ msgstr "" msgid "Register" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:161 +#: lib/who_need_help_web/components/layouts.ex:165 #, elixir-autogen, elixir-format msgid "Requests" msgstr "" @@ -93,12 +93,12 @@ msgstr "" msgid "See nearby requests" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:302 +#: lib/who_need_help_web/components/layouts.ex:311 #, elixir-autogen, elixir-format msgid "Something went wrong!" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:287 +#: lib/who_need_help_web/components/layouts.ex:296 #, elixir-autogen, elixir-format msgid "We can't find the internet" msgstr "" @@ -174,7 +174,7 @@ msgid "Account Settings" msgstr "" #: lib/who_need_help_web/components/layouts.ex:63 -#: lib/who_need_help_web/components/layouts.ex:192 +#: lib/who_need_help_web/components/layouts.ex:201 #, elixir-autogen, elixir-format msgid "Account and language" msgstr "" @@ -195,7 +195,7 @@ msgstr "" msgid "Active" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:166 +#: lib/who_need_help_web/components/layouts.ex:170 #: lib/who_need_help_web/live/activity_live/index.ex:118 #, elixir-autogen, elixir-format msgid "Activities" @@ -367,7 +367,7 @@ msgstr "" msgid "Briefly describe the help you need" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:147 +#: lib/who_need_help_web/components/layouts.ex:151 #, elixir-autogen, elixir-format msgid "Build Week feedback" msgstr "" @@ -641,7 +641,7 @@ msgstr "" msgid "Dismissed" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:76 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -662,7 +662,7 @@ msgstr "" msgid "Double-blind review" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:67 #: 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 @@ -817,7 +817,7 @@ msgstr "" msgid "Helper: %{name}" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:176 +#: lib/who_need_help_web/components/layouts.ex:180 #, elixir-autogen, elixir-format msgid "Helpers" msgstr "" @@ -872,7 +872,7 @@ msgid "Human decisions retained" msgstr "" #: 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "" @@ -913,9 +913,12 @@ msgstr "" msgid "If your email is in our system, you will receive instructions for logging in shortly." msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:9 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:25 #: 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:1109 +#: lib/who_need_help_web/live/support_operations_live.ex:91 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "" @@ -1122,7 +1125,7 @@ msgstr "" msgid "Mode" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:181 +#: lib/who_need_help_web/components/layouts.ex:185 #: lib/who_need_help_web/live/moderation_live.ex:9 #: lib/who_need_help_web/live/moderation_live.ex:291 #, elixir-autogen, elixir-format @@ -1136,6 +1139,7 @@ msgid "Moderator" msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:230 +#: lib/who_need_help_web/live/support_operations_live.ex:73 #: lib/who_need_help_web/user_auth.ex:306 #, elixir-autogen, elixir-format msgid "Moderator access is required." @@ -1270,12 +1274,16 @@ msgstr "" msgid "Only the organizer and approved participants can read or send these messages." msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:21 +#: lib/who_need_help_web/controllers/support_html.ex:16 #: lib/who_need_help_web/live/activity_live/index.ex:166 #: 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:636 +#: lib/who_need_help_web/live/support_operations_live.ex:189 +#: lib/who_need_help_web/live/support_operations_live.ex:252 #, elixir-autogen, elixir-format msgid "Open" msgstr "" @@ -1320,10 +1328,17 @@ msgstr "" msgid "Organizer social links" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:18 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:29 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:49 +#: lib/who_need_help_web/controllers/support_html.ex:13 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:35 #: 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:1110 +#: lib/who_need_help_web/live/support_operations_live.ex:85 +#: lib/who_need_help_web/live/support_operations_live.ex:100 #, elixir-autogen, elixir-format msgid "Other" msgstr "" @@ -1374,6 +1389,7 @@ msgid "Please check the entered data." msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:240 +#: lib/who_need_help_web/live/support_operations_live.ex:75 #, elixir-autogen, elixir-format msgid "Please check the submitted fields." msgstr "" @@ -1497,6 +1513,7 @@ msgid "READ BEFORE USING THE SERVICE" msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:290 +#: lib/who_need_help_web/live/support_operations_live.ex:111 #, elixir-autogen, elixir-format msgid "RESTRICTED WORKSPACE" msgstr "" @@ -1517,11 +1534,12 @@ msgstr "" msgid "Rating" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:82 #, elixir-autogen, elixir-format msgid "Read the" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:20 #: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" @@ -1633,8 +1651,10 @@ msgstr "" msgid "Resolution note" msgstr "" +#: lib/who_need_help_web/controllers/support_html.ex:19 #: lib/who_need_help_web/live/moderation_live.ex:259 #: lib/who_need_help_web/live/moderation_live.ex:365 +#: lib/who_need_help_web/live/support_operations_live.ex:255 #, elixir-autogen, elixir-format msgid "Resolved" msgstr "" @@ -1662,8 +1682,12 @@ msgstr "" msgid "Reviewed" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:23 +#: lib/who_need_help_web/controllers/support_html.ex:17 #: lib/who_need_help_web/live/moderation_live.ex:258 #: lib/who_need_help_web/live/moderation_live.ex:364 +#: lib/who_need_help_web/live/support_operations_live.ex:191 +#: lib/who_need_help_web/live/support_operations_live.ex:253 #, elixir-autogen, elixir-format msgid "Reviewing" msgstr "" @@ -2129,12 +2153,12 @@ msgstr "" msgid "Use a public first meeting point. Do not publish a home address, access code, travel document, or other sensitive information." msgstr "" -#: lib/who_need_help_web/components/layouts.ex:349 +#: lib/who_need_help_web/components/layouts.ex:358 #, elixir-autogen, elixir-format msgid "Use dark theme" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:340 +#: lib/who_need_help_web/components/layouts.ex:349 #, elixir-autogen, elixir-format msgid "Use light theme" msgstr "" @@ -2150,7 +2174,7 @@ msgstr "" msgid "Use private chat and optional live location sharing." msgstr "" -#: lib/who_need_help_web/components/layouts.ex:331 +#: lib/who_need_help_web/components/layouts.ex:340 #, elixir-autogen, elixir-format msgid "Use system theme" msgstr "" @@ -2266,7 +2290,7 @@ msgstr "" msgid "Who Need Help home" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 #, 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 "" @@ -2407,6 +2431,7 @@ msgstr "" msgid "Your request is now visible to nearby helpers." msgstr "" +#: lib/who_need_help_web/controllers/support_html/index.html.heex:6 #: lib/who_need_help_web/live/request_live/index.ex:342 #, elixir-autogen, elixir-format msgid "Your requests" @@ -2432,7 +2457,7 @@ msgstr "" msgid "activity message" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:84 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "" @@ -2509,7 +2534,7 @@ msgstr "" msgid "rating %{rating}" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:83 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "" @@ -2617,7 +2642,9 @@ msgstr "" msgid "Reciprocal rating ring" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:26 #: lib/who_need_help_web/live/moderation_live.ex:263 +#: lib/who_need_help_web/live/support_operations_live.ex:194 #, elixir-autogen, elixir-format msgid "Rejected" msgstr "" @@ -2650,6 +2677,8 @@ msgstr "" #: lib/who_need_help_web/live/profile_live.ex:354 #: lib/who_need_help_web/live/request_live/index.ex:303 #: lib/who_need_help_web/live/request_live/index.ex:360 +#: lib/who_need_help_web/live/support_operations_live.ex:215 +#: lib/who_need_help_web/live/support_operations_live.ex:276 #, elixir-autogen, elixir-format msgid "Load more" msgstr "" @@ -2736,6 +2765,7 @@ msgid "The registration form is invalid." msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:239 +#: lib/who_need_help_web/live/support_operations_live.ex:74 #, elixir-autogen, elixir-format msgid "The selected record is no longer available." msgstr "" @@ -2835,7 +2865,7 @@ msgstr "" 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:101 #, elixir-autogen, elixir-format msgid "Create account and email me a link" msgstr "" @@ -2897,7 +2927,7 @@ msgstr "" 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:100 #, elixir-autogen, elixir-format msgid "Sending link..." msgstr "" @@ -2981,3 +3011,632 @@ msgstr "" #, elixir-autogen, elixir-format msgid "No Google account is connected." msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:45 +#, elixir-autogen, elixir-format +msgid "A person I am authorised to represent" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:8 +#, elixir-autogen, elixir-format +msgid "A private status link has been sent when a contact email was provided and delivery is configured. Opening that link verifies the contact address. Keep the reference for follow-up." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:3 +#, elixir-autogen, elixir-format +msgid "ACCOUNT AND DATA" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:6 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:29 +#: lib/who_need_help_web/live/support_operations_live.ex:78 +#, elixir-autogen, elixir-format +msgid "Account access" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:115 +#, elixir-autogen, elixir-format +msgid "Account and personal data" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:10 +#: lib/who_need_help_web/live/support_operations_live.ex:82 +#, elixir-autogen, elixir-format +msgid "Account deletion" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:26 +#, elixir-autogen, elixir-format +msgid "Account email" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:193 +#, elixir-autogen, elixir-format +msgid "Action taken" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Additional information" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:32 +#, elixir-autogen, elixir-format +msgid "Appeal a moderation decision" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Back to removal form" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Back to support" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "CONTENT REMOVAL" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Check your email" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:27 +#, elixir-autogen, elixir-format +msgid "Choose a category" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:31 +#, elixir-autogen, elixir-format +msgid "Choose a reason" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:51 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:47 +#, elixir-autogen, elixir-format +msgid "Choose a relationship" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:37 +#, elixir-autogen, elixir-format +msgid "Choose a topic" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:27 +#: lib/who_need_help_web/controllers/support_html.ex:20 +#: lib/who_need_help_web/live/support_operations_live.ex:195 +#: lib/who_need_help_web/live/support_operations_live.ex:256 +#, elixir-autogen, elixir-format +msgid "Closed" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:160 +#, elixir-autogen, elixir-format +msgid "Contact" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Contact Who Need Help" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:38 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:34 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Contact email" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:127 +#, elixir-autogen, elixir-format +msgid "Content removal and TAKE IT DOWN" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Content-removal notice" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:8 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:90 +#, elixir-autogen, elixir-format +msgid "Copyright" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:76 +#, elixir-autogen, elixir-format +msgid "Could not update the request. Please try again." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:11 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:34 +#: lib/who_need_help_web/live/support_operations_live.ex:83 +#, elixir-autogen, elixir-format +msgid "Data export" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:201 +#, elixir-autogen, elixir-format +msgid "Decision or information request" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Decision or request for information" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:19 +#, elixir-autogen, elixir-format +msgid "Delete an account" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:52 +#, elixir-autogen, elixir-format +msgid "Describe the problem" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:56 +#, elixir-autogen, elixir-format +msgid "Do not include passwords, payment card details, access codes, prescriptions, or unnecessary medical information." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Do not upload the image or video. Provide only the exact Who Need Help URL where it appears and enough text to identify it safely." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:72 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Electronic signature (type your full name)" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:56 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Exact content URLs — one per line" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:220 +#, elixir-autogen, elixir-format +msgid "General support and data requests" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "I am an authorised representative" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:47 +#, elixir-autogen, elixir-format +msgid "I am the affected person or rights holder" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:78 +#, elixir-autogen, elixir-format +msgid "I believe in good faith that the identified content is illegal or infringes the stated right." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:84 +#, elixir-autogen, elixir-format +msgid "I confirm that the information in this request is accurate and that I am the depicted person or authorised to act for them." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:87 +#, elixir-autogen, elixir-format +msgid "I confirm that this notice is accurate and complete to the best of my knowledge." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:75 +#, elixir-autogen, elixir-format +msgid "I have a good-faith belief that this intimate visual material was shared without consent." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Identify the material without reproducing it and explain why it is non-consensual" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:93 +#, elixir-autogen, elixir-format +msgid "Identity and contact fields may be omitted for reports of sexual material involving a minor. Do not include or upload the material itself." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:6 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:88 +#, elixir-autogen, elixir-format +msgid "Illegal content" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:20 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. A copy is sent to the contact email when email delivery is configured." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. The submitter identity is not shown publicly or automatically disclosed to the person who posted the reported content." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Law, right, or policy involved, if known" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:162 +#, elixir-autogen, elixir-format +msgid "Locations" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:22 +#, elixir-autogen, elixir-format +msgid "Material involved" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:9 +#: lib/who_need_help_web/live/support_operations_live.ex:81 +#, elixir-autogen, elixir-format +msgid "Moderation appeal" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:192 +#, elixir-autogen, elixir-format +msgid "More information needed" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:44 +#, elixir-autogen, elixir-format +msgid "Myself" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "NOTICE RECEIVED" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:8 +#, elixir-autogen, elixir-format +msgid "New request" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:207 +#, elixir-autogen, elixir-format +msgid "No removal notices." +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:268 +#, elixir-autogen, elixir-format +msgid "No support requests." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:12 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:26 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:94 +#, elixir-autogen, elixir-format +msgid "Non-consensual intimate image or video" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:159 +#: lib/who_need_help_web/live/support_operations_live.ex:161 +#, elixir-autogen, elixir-format +msgid "Not provided" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:12 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:33 +#: lib/who_need_help_web/live/support_operations_live.ex:84 +#, elixir-autogen, elixir-format +msgid "Privacy request" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:7 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:23 +#: lib/who_need_help_web/live/support_operations_live.ex:89 +#, elixir-autogen, elixir-format +msgid "Privacy violation" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Provide exact Who Need Help URLs. Do not upload or paste intimate imagery, identity documents, passwords, access codes, or unnecessary medical information." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "REQUEST RECEIVED" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:24 +#, elixir-autogen, elixir-format +msgid "Removal notice updated." +msgstr "" + +#: lib/who_need_help_web/components/layouts.ex:149 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Report content" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Report illegal or harmful content" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:16 +#, elixir-autogen, elixir-format +msgid "Reported locations" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:4 +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:37 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:123 +#, elixir-autogen, elixir-format +msgid "Request account deletion" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:117 +#, elixir-autogen, elixir-format +msgid "Request deletion of your account and associated personal data, or contact support about access and export requests." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Request removal of intimate visual material" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:5 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "SUPPORT" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:8 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:31 +#: lib/who_need_help_web/live/support_operations_live.ex:80 +#, elixir-autogen, elixir-format +msgid "Safety concern" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:203 +#: lib/who_need_help_web/live/support_operations_live.ex:264 +#, elixir-autogen, elixir-format +msgid "Save and notify" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Send support request" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:15 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:27 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:97 +#, elixir-autogen, elixir-format +msgid "Sexual material involving a minor" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:177 +#, elixir-autogen, elixir-format +msgid "Stated basis:" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "Subject" +msgstr "" + +#: lib/who_need_help_web/live/activity_live/show.ex:696 +#: lib/who_need_help_web/live/request_live/show.ex:1127 +#, elixir-autogen, elixir-format +msgid "Submit a legal content-removal notice" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:97 +#, elixir-autogen, elixir-format +msgid "Submit removal notice" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:89 +#, elixir-autogen, elixir-format +msgid "Submit urgent removal request" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:158 +#, elixir-autogen, elixir-format +msgid "Submitter" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Submitting this form does not immediately sign you out or erase evidence required for an open safety investigation. Support will verify and process the request." +msgstr "" + +#: lib/who_need_help_web/components/layouts.ex:147 +#, elixir-autogen, elixir-format +msgid "Support" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:8 +#: lib/who_need_help_web/live/support_operations_live.ex:114 +#, elixir-autogen, elixir-format +msgid "Support operations" +msgstr "" + +#: lib/who_need_help_web/components/layouts.ex:190 +#, elixir-autogen, elixir-format +msgid "Support queue" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:16 +#, elixir-autogen, elixir-format +msgid "Support request updated." +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:116 +#, elixir-autogen, elixir-format +msgid "Support requests and legal removal notices are separate queues. Decisions and operator identity are audited." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:16 +#: lib/who_need_help_web/live/support_operations_live.ex:262 +#, elixir-autogen, elixir-format +msgid "Support response" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:3 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:16 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN request" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:7 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:30 +#: lib/who_need_help_web/live/support_operations_live.ex:79 +#, elixir-autogen, elixir-format +msgid "Technical issue" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6 +#, elixir-autogen, elixir-format +msgid "This requests deletion of your Who Need Help account and associated personal data. Some records may need to be retained when required for safety, fraud prevention, legal obligations, or resolving an active dispute; any retained data must be described in the applicable retention notice." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:17 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:28 +#: lib/who_need_help_web/live/support_operations_live.ex:99 +#, elixir-autogen, elixir-format +msgid "Threat to life or safety" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_controller.ex:54 +#, elixir-autogen, elixir-format +msgid "Too many removal notices. Please try again later." +msgstr "" + +#: lib/who_need_help_web/controllers/support_controller.ex:34 +#: lib/who_need_help_web/controllers/support_controller.ex:72 +#, elixir-autogen, elixir-format +msgid "Too many support requests. Please try again later." +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Trust and safety moderation" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:190 +#, elixir-autogen, elixir-format +msgid "Urgent review" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:8 +#, elixir-autogen, elixir-format +msgid "Use this dedicated process when an identifiable person is shown in an intimate image or video shared without consent, including a digital forgery. You may submit for yourself or as an authorised representative." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this electronic notice for specific content on Who Need Help that you believe is illegal or infringes your rights. Ordinary safety reports inside a request or chat remain available to signed-in users." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this form for account access, technical problems, privacy questions, or other support. For illegal content or a removal request, use the dedicated removal form." +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:126 +#, elixir-autogen, elixir-format +msgid "View support requests" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:254 +#, elixir-autogen, elixir-format +msgid "Waiting for requester" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:18 +#, elixir-autogen, elixir-format +msgid "Waiting for your response" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:6 +#, elixir-autogen, elixir-format +msgid "We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:27 +#, elixir-autogen, elixir-format +msgid "What do you need help with?" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:42 +#, elixir-autogen, elixir-format +msgid "Who are you submitting for?" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:62 +#, elixir-autogen, elixir-format +msgid "Why do you believe this content should be removed?" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:26 +#, elixir-autogen, elixir-format +msgid "You have not submitted any support requests." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Your full name" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Your name or organisation" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Your reference is %{reference}" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:45 +#, elixir-autogen, elixir-format +msgid "Your relationship to the affected person or rights holder" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:150 +#: lib/who_need_help_web/live/support_operations_live.ex:235 +#, elixir-autogen, elixir-format +msgid "contact unverified" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:154 +#, elixir-autogen, elixir-format +msgid "review by %{time}" +msgstr "" + +#: lib/who_need_help_web/controllers/support_controller.ex:47 +#: lib/who_need_help_web/controllers/support_controller.ex:58 +#, elixir-autogen, elixir-format +msgid "Delete my Who Need Help account" +msgstr "" + +#: lib/who_need_help_web/controllers/support_controller.ex:48 +#, elixir-autogen, elixir-format +msgid "Please delete my account and associated personal data." +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 6aac897..2771e8b 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -12,7 +12,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/who_need_help_web/components/layouts.ex:111 -#: lib/who_need_help_web/components/layouts.ex:257 +#: lib/who_need_help_web/components/layouts.ex:266 #, elixir-autogen, elixir-format msgid "Account settings" msgstr "" @@ -30,13 +30,13 @@ msgstr "" msgid "Ask for help" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:295 -#: lib/who_need_help_web/components/layouts.ex:310 +#: lib/who_need_help_web/components/layouts.ex:304 +#: lib/who_need_help_web/components/layouts.ex:319 #, elixir-autogen, elixir-format msgid "Attempting to reconnect" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:171 +#: lib/who_need_help_web/components/layouts.ex:175 #, elixir-autogen, elixir-format msgid "Categories" msgstr "" @@ -65,13 +65,13 @@ msgid "Log in" msgstr "" #: lib/who_need_help_web/components/layouts.ex:114 -#: lib/who_need_help_web/components/layouts.ex:260 +#: lib/who_need_help_web/components/layouts.ex:269 #, elixir-autogen, elixir-format msgid "Log out" msgstr "" #: lib/who_need_help_web/components/layouts.ex:108 -#: lib/who_need_help_web/components/layouts.ex:254 +#: lib/who_need_help_web/components/layouts.ex:263 #: lib/who_need_help_web/live/profile_live.ex:16 #, elixir-autogen, elixir-format msgid "Profile" @@ -83,7 +83,7 @@ msgstr "" msgid "Register" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:161 +#: lib/who_need_help_web/components/layouts.ex:165 #, elixir-autogen, elixir-format msgid "Requests" msgstr "" @@ -93,12 +93,12 @@ msgstr "" msgid "See nearby requests" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:302 +#: lib/who_need_help_web/components/layouts.ex:311 #, elixir-autogen, elixir-format msgid "Something went wrong!" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:287 +#: lib/who_need_help_web/components/layouts.ex:296 #, elixir-autogen, elixir-format msgid "We can't find the internet" msgstr "" @@ -174,7 +174,7 @@ msgid "Account Settings" msgstr "" #: lib/who_need_help_web/components/layouts.ex:63 -#: lib/who_need_help_web/components/layouts.ex:192 +#: lib/who_need_help_web/components/layouts.ex:201 #, elixir-autogen, elixir-format msgid "Account and language" msgstr "" @@ -195,7 +195,7 @@ msgstr "" msgid "Active" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:166 +#: lib/who_need_help_web/components/layouts.ex:170 #: lib/who_need_help_web/live/activity_live/index.ex:118 #, elixir-autogen, elixir-format msgid "Activities" @@ -367,7 +367,7 @@ msgstr "" msgid "Briefly describe the help you need" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:147 +#: lib/who_need_help_web/components/layouts.ex:151 #, elixir-autogen, elixir-format msgid "Build Week feedback" msgstr "" @@ -641,7 +641,7 @@ msgstr "" msgid "Dismissed" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:76 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -662,7 +662,7 @@ msgstr "" msgid "Double-blind review" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:67 #: 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 @@ -817,7 +817,7 @@ msgstr "" msgid "Helper: %{name}" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:176 +#: lib/who_need_help_web/components/layouts.ex:180 #, elixir-autogen, elixir-format msgid "Helpers" msgstr "" @@ -872,7 +872,7 @@ msgid "Human decisions retained" msgstr "" #: 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "" @@ -913,9 +913,12 @@ msgstr "" msgid "If your email is in our system, you will receive instructions for logging in shortly." msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:9 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:25 #: 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:1109 +#: lib/who_need_help_web/live/support_operations_live.ex:91 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "" @@ -1122,7 +1125,7 @@ msgstr "" msgid "Mode" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:181 +#: lib/who_need_help_web/components/layouts.ex:185 #: lib/who_need_help_web/live/moderation_live.ex:9 #: lib/who_need_help_web/live/moderation_live.ex:291 #, elixir-autogen, elixir-format @@ -1136,6 +1139,7 @@ msgid "Moderator" msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:230 +#: lib/who_need_help_web/live/support_operations_live.ex:73 #: lib/who_need_help_web/user_auth.ex:306 #, elixir-autogen, elixir-format msgid "Moderator access is required." @@ -1270,12 +1274,16 @@ msgstr "" msgid "Only the organizer and approved participants can read or send these messages." msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:21 +#: lib/who_need_help_web/controllers/support_html.ex:16 #: lib/who_need_help_web/live/activity_live/index.ex:166 #: 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:636 +#: lib/who_need_help_web/live/support_operations_live.ex:189 +#: lib/who_need_help_web/live/support_operations_live.ex:252 #, elixir-autogen, elixir-format msgid "Open" msgstr "" @@ -1320,10 +1328,17 @@ msgstr "" msgid "Organizer social links" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:18 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:29 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:49 +#: lib/who_need_help_web/controllers/support_html.ex:13 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:35 #: 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:1110 +#: lib/who_need_help_web/live/support_operations_live.ex:85 +#: lib/who_need_help_web/live/support_operations_live.ex:100 #, elixir-autogen, elixir-format msgid "Other" msgstr "" @@ -1374,6 +1389,7 @@ msgid "Please check the entered data." msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:240 +#: lib/who_need_help_web/live/support_operations_live.ex:75 #, elixir-autogen, elixir-format msgid "Please check the submitted fields." msgstr "" @@ -1497,6 +1513,7 @@ msgid "READ BEFORE USING THE SERVICE" msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:290 +#: lib/who_need_help_web/live/support_operations_live.ex:111 #, elixir-autogen, elixir-format msgid "RESTRICTED WORKSPACE" msgstr "" @@ -1517,11 +1534,12 @@ msgstr "" msgid "Rating" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:82 #, elixir-autogen, elixir-format msgid "Read the" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:20 #: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" @@ -1633,8 +1651,10 @@ msgstr "" msgid "Resolution note" msgstr "" +#: lib/who_need_help_web/controllers/support_html.ex:19 #: lib/who_need_help_web/live/moderation_live.ex:259 #: lib/who_need_help_web/live/moderation_live.ex:365 +#: lib/who_need_help_web/live/support_operations_live.ex:255 #, elixir-autogen, elixir-format msgid "Resolved" msgstr "" @@ -1662,8 +1682,12 @@ msgstr "" msgid "Reviewed" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:23 +#: lib/who_need_help_web/controllers/support_html.ex:17 #: lib/who_need_help_web/live/moderation_live.ex:258 #: lib/who_need_help_web/live/moderation_live.ex:364 +#: lib/who_need_help_web/live/support_operations_live.ex:191 +#: lib/who_need_help_web/live/support_operations_live.ex:253 #, elixir-autogen, elixir-format msgid "Reviewing" msgstr "" @@ -2129,12 +2153,12 @@ msgstr "" msgid "Use a public first meeting point. Do not publish a home address, access code, travel document, or other sensitive information." msgstr "" -#: lib/who_need_help_web/components/layouts.ex:349 +#: lib/who_need_help_web/components/layouts.ex:358 #, elixir-autogen, elixir-format msgid "Use dark theme" msgstr "" -#: lib/who_need_help_web/components/layouts.ex:340 +#: lib/who_need_help_web/components/layouts.ex:349 #, elixir-autogen, elixir-format msgid "Use light theme" msgstr "" @@ -2150,7 +2174,7 @@ msgstr "" msgid "Use private chat and optional live location sharing." msgstr "" -#: lib/who_need_help_web/components/layouts.ex:331 +#: lib/who_need_help_web/components/layouts.ex:340 #, elixir-autogen, elixir-format msgid "Use system theme" msgstr "" @@ -2266,7 +2290,7 @@ msgstr "" msgid "Who Need Help home" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 #, 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 "" @@ -2407,6 +2431,7 @@ msgstr "" msgid "Your request is now visible to nearby helpers." msgstr "" +#: lib/who_need_help_web/controllers/support_html/index.html.heex:6 #: lib/who_need_help_web/live/request_live/index.ex:342 #, elixir-autogen, elixir-format msgid "Your requests" @@ -2432,7 +2457,7 @@ msgstr "" msgid "activity message" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:84 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "" @@ -2509,7 +2534,7 @@ msgstr "" msgid "rating %{rating}" msgstr "" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:83 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "" @@ -2617,7 +2642,9 @@ msgstr "" msgid "Reciprocal rating ring" msgstr "" +#: lib/who_need_help_web/controllers/content_removal_html.ex:26 #: lib/who_need_help_web/live/moderation_live.ex:263 +#: lib/who_need_help_web/live/support_operations_live.ex:194 #, elixir-autogen, elixir-format msgid "Rejected" msgstr "" @@ -2650,6 +2677,8 @@ msgstr "" #: lib/who_need_help_web/live/profile_live.ex:354 #: lib/who_need_help_web/live/request_live/index.ex:303 #: lib/who_need_help_web/live/request_live/index.ex:360 +#: lib/who_need_help_web/live/support_operations_live.ex:215 +#: lib/who_need_help_web/live/support_operations_live.ex:276 #, elixir-autogen, elixir-format msgid "Load more" msgstr "" @@ -2736,6 +2765,7 @@ msgid "The registration form is invalid." msgstr "" #: lib/who_need_help_web/live/moderation_live.ex:239 +#: lib/who_need_help_web/live/support_operations_live.ex:74 #, elixir-autogen, elixir-format msgid "The selected record is no longer available." msgstr "" @@ -2835,7 +2865,7 @@ msgstr "" 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:101 #, elixir-autogen, elixir-format msgid "Create account and email me a link" msgstr "" @@ -2897,7 +2927,7 @@ msgstr "" 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:100 #, elixir-autogen, elixir-format msgid "Sending link..." msgstr "" @@ -2981,3 +3011,632 @@ msgstr "Google sign-in disconnected." #, elixir-autogen, elixir-format msgid "No Google account is connected." msgstr "No Google account is connected." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:45 +#, elixir-autogen, elixir-format +msgid "A person I am authorised to represent" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:8 +#, elixir-autogen, elixir-format +msgid "A private status link has been sent when a contact email was provided and delivery is configured. Opening that link verifies the contact address. Keep the reference for follow-up." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:3 +#, elixir-autogen, elixir-format +msgid "ACCOUNT AND DATA" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:6 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:29 +#: lib/who_need_help_web/live/support_operations_live.ex:78 +#, elixir-autogen, elixir-format, fuzzy +msgid "Account access" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:115 +#, elixir-autogen, elixir-format +msgid "Account and personal data" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:10 +#: lib/who_need_help_web/live/support_operations_live.ex:82 +#, elixir-autogen, elixir-format, fuzzy +msgid "Account deletion" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:26 +#, elixir-autogen, elixir-format, fuzzy +msgid "Account email" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:193 +#, elixir-autogen, elixir-format, fuzzy +msgid "Action taken" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Additional information" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:32 +#, elixir-autogen, elixir-format +msgid "Appeal a moderation decision" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Back to removal form" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Back to support" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "CONTENT REMOVAL" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Check your email" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:27 +#, elixir-autogen, elixir-format, fuzzy +msgid "Choose a category" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:31 +#, elixir-autogen, elixir-format +msgid "Choose a reason" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:51 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:47 +#, elixir-autogen, elixir-format +msgid "Choose a relationship" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:37 +#, elixir-autogen, elixir-format +msgid "Choose a topic" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:27 +#: lib/who_need_help_web/controllers/support_html.ex:20 +#: lib/who_need_help_web/live/support_operations_live.ex:195 +#: lib/who_need_help_web/live/support_operations_live.ex:256 +#, elixir-autogen, elixir-format, fuzzy +msgid "Closed" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:160 +#, elixir-autogen, elixir-format +msgid "Contact" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Contact Who Need Help" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:38 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:34 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Contact email" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:127 +#, elixir-autogen, elixir-format +msgid "Content removal and TAKE IT DOWN" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Content-removal notice" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:8 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:90 +#, elixir-autogen, elixir-format +msgid "Copyright" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:76 +#, elixir-autogen, elixir-format, fuzzy +msgid "Could not update the request. Please try again." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:11 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:34 +#: lib/who_need_help_web/live/support_operations_live.ex:83 +#, elixir-autogen, elixir-format +msgid "Data export" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:201 +#, elixir-autogen, elixir-format +msgid "Decision or information request" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Decision or request for information" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:19 +#, elixir-autogen, elixir-format +msgid "Delete an account" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:52 +#, elixir-autogen, elixir-format +msgid "Describe the problem" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:56 +#, elixir-autogen, elixir-format +msgid "Do not include passwords, payment card details, access codes, prescriptions, or unnecessary medical information." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Do not upload the image or video. Provide only the exact Who Need Help URL where it appears and enough text to identify it safely." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:72 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Electronic signature (type your full name)" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:56 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Exact content URLs — one per line" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:220 +#, elixir-autogen, elixir-format +msgid "General support and data requests" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "I am an authorised representative" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:47 +#, elixir-autogen, elixir-format +msgid "I am the affected person or rights holder" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:78 +#, elixir-autogen, elixir-format +msgid "I believe in good faith that the identified content is illegal or infringes the stated right." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:84 +#, elixir-autogen, elixir-format +msgid "I confirm that the information in this request is accurate and that I am the depicted person or authorised to act for them." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:87 +#, elixir-autogen, elixir-format +msgid "I confirm that this notice is accurate and complete to the best of my knowledge." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:75 +#, elixir-autogen, elixir-format +msgid "I have a good-faith belief that this intimate visual material was shared without consent." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Identify the material without reproducing it and explain why it is non-consensual" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:93 +#, elixir-autogen, elixir-format +msgid "Identity and contact fields may be omitted for reports of sexual material involving a minor. Do not include or upload the material itself." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:6 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:88 +#, elixir-autogen, elixir-format +msgid "Illegal content" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:20 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. A copy is sent to the contact email when email delivery is configured." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. The submitter identity is not shown publicly or automatically disclosed to the person who posted the reported content." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Law, right, or policy involved, if known" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:162 +#, elixir-autogen, elixir-format, fuzzy +msgid "Locations" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:22 +#, elixir-autogen, elixir-format +msgid "Material involved" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:9 +#: lib/who_need_help_web/live/support_operations_live.ex:81 +#, elixir-autogen, elixir-format, fuzzy +msgid "Moderation appeal" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:192 +#, elixir-autogen, elixir-format +msgid "More information needed" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:44 +#, elixir-autogen, elixir-format +msgid "Myself" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "NOTICE RECEIVED" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:8 +#, elixir-autogen, elixir-format, fuzzy +msgid "New request" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:207 +#, elixir-autogen, elixir-format +msgid "No removal notices." +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:268 +#, elixir-autogen, elixir-format +msgid "No support requests." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:12 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:26 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:94 +#, elixir-autogen, elixir-format +msgid "Non-consensual intimate image or video" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:159 +#: lib/who_need_help_web/live/support_operations_live.ex:161 +#, elixir-autogen, elixir-format +msgid "Not provided" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:12 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:33 +#: lib/who_need_help_web/live/support_operations_live.ex:84 +#, elixir-autogen, elixir-format +msgid "Privacy request" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:7 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:23 +#: lib/who_need_help_web/live/support_operations_live.ex:89 +#, elixir-autogen, elixir-format +msgid "Privacy violation" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Provide exact Who Need Help URLs. Do not upload or paste intimate imagery, identity documents, passwords, access codes, or unnecessary medical information." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "REQUEST RECEIVED" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:24 +#, elixir-autogen, elixir-format +msgid "Removal notice updated." +msgstr "" + +#: lib/who_need_help_web/components/layouts.ex:149 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:13 +#, elixir-autogen, elixir-format, fuzzy +msgid "Report content" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Report illegal or harmful content" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:16 +#, elixir-autogen, elixir-format +msgid "Reported locations" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:4 +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:37 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:123 +#, elixir-autogen, elixir-format, fuzzy +msgid "Request account deletion" +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:117 +#, elixir-autogen, elixir-format +msgid "Request deletion of your account and associated personal data, or contact support about access and export requests." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Request removal of intimate visual material" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:5 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "SUPPORT" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:8 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:31 +#: lib/who_need_help_web/live/support_operations_live.ex:80 +#, elixir-autogen, elixir-format, fuzzy +msgid "Safety concern" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:203 +#: lib/who_need_help_web/live/support_operations_live.ex:264 +#, elixir-autogen, elixir-format +msgid "Save and notify" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Send support request" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:15 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:27 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:97 +#, elixir-autogen, elixir-format +msgid "Sexual material involving a minor" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:177 +#, elixir-autogen, elixir-format +msgid "Stated basis:" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "Subject" +msgstr "" + +#: lib/who_need_help_web/live/activity_live/show.ex:696 +#: lib/who_need_help_web/live/request_live/show.ex:1127 +#, elixir-autogen, elixir-format +msgid "Submit a legal content-removal notice" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:97 +#, elixir-autogen, elixir-format, fuzzy +msgid "Submit removal notice" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:89 +#, elixir-autogen, elixir-format +msgid "Submit urgent removal request" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:158 +#, elixir-autogen, elixir-format +msgid "Submitter" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Submitting this form does not immediately sign you out or erase evidence required for an open safety investigation. Support will verify and process the request." +msgstr "" + +#: lib/who_need_help_web/components/layouts.ex:147 +#, elixir-autogen, elixir-format +msgid "Support" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:8 +#: lib/who_need_help_web/live/support_operations_live.ex:114 +#, elixir-autogen, elixir-format +msgid "Support operations" +msgstr "" + +#: lib/who_need_help_web/components/layouts.ex:190 +#, elixir-autogen, elixir-format +msgid "Support queue" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:16 +#, elixir-autogen, elixir-format +msgid "Support request updated." +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:116 +#, elixir-autogen, elixir-format +msgid "Support requests and legal removal notices are separate queues. Decisions and operator identity are audited." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:16 +#: lib/who_need_help_web/live/support_operations_live.ex:262 +#, elixir-autogen, elixir-format +msgid "Support response" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:3 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:16 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN request" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:7 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:30 +#: lib/who_need_help_web/live/support_operations_live.ex:79 +#, elixir-autogen, elixir-format +msgid "Technical issue" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6 +#, elixir-autogen, elixir-format +msgid "This requests deletion of your Who Need Help account and associated personal data. Some records may need to be retained when required for safety, fraud prevention, legal obligations, or resolving an active dispute; any retained data must be described in the applicable retention notice." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:17 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:28 +#: lib/who_need_help_web/live/support_operations_live.ex:99 +#, elixir-autogen, elixir-format +msgid "Threat to life or safety" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_controller.ex:54 +#, elixir-autogen, elixir-format +msgid "Too many removal notices. Please try again later." +msgstr "" + +#: lib/who_need_help_web/controllers/support_controller.ex:34 +#: lib/who_need_help_web/controllers/support_controller.ex:72 +#, elixir-autogen, elixir-format +msgid "Too many support requests. Please try again later." +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Trust and safety moderation" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:190 +#, elixir-autogen, elixir-format +msgid "Urgent review" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:8 +#, elixir-autogen, elixir-format +msgid "Use this dedicated process when an identifiable person is shown in an intimate image or video shared without consent, including a digital forgery. You may submit for yourself or as an authorised representative." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this electronic notice for specific content on Who Need Help that you believe is illegal or infringes your rights. Ordinary safety reports inside a request or chat remain available to signed-in users." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this form for account access, technical problems, privacy questions, or other support. For illegal content or a removal request, use the dedicated removal form." +msgstr "" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:126 +#, elixir-autogen, elixir-format +msgid "View support requests" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:254 +#, elixir-autogen, elixir-format, fuzzy +msgid "Waiting for requester" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html.ex:18 +#, elixir-autogen, elixir-format +msgid "Waiting for your response" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:6 +#, elixir-autogen, elixir-format +msgid "We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests." +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:27 +#, elixir-autogen, elixir-format, fuzzy +msgid "What do you need help with?" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:42 +#, elixir-autogen, elixir-format +msgid "Who are you submitting for?" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:62 +#, elixir-autogen, elixir-format +msgid "Why do you believe this content should be removed?" +msgstr "" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:26 +#, elixir-autogen, elixir-format +msgid "You have not submitted any support requests." +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Your full name" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Your name or organisation" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Your reference is %{reference}" +msgstr "" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:45 +#, elixir-autogen, elixir-format +msgid "Your relationship to the affected person or rights holder" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:150 +#: lib/who_need_help_web/live/support_operations_live.ex:235 +#, elixir-autogen, elixir-format, fuzzy +msgid "contact unverified" +msgstr "" + +#: lib/who_need_help_web/live/support_operations_live.ex:154 +#, elixir-autogen, elixir-format +msgid "review by %{time}" +msgstr "" + +#: lib/who_need_help_web/controllers/support_controller.ex:47 +#: lib/who_need_help_web/controllers/support_controller.ex:58 +#, elixir-autogen, elixir-format +msgid "Delete my Who Need Help account" +msgstr "" + +#: lib/who_need_help_web/controllers/support_controller.ex:48 +#, elixir-autogen, elixir-format +msgid "Please delete my account and associated personal data." +msgstr "" diff --git a/priv/gettext/ru/LC_MESSAGES/default.po b/priv/gettext/ru/LC_MESSAGES/default.po index 2bd4d4c..55b9236 100644 --- a/priv/gettext/ru/LC_MESSAGES/default.po +++ b/priv/gettext/ru/LC_MESSAGES/default.po @@ -12,7 +12,7 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100 != 11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n" #: lib/who_need_help_web/components/layouts.ex:111 -#: lib/who_need_help_web/components/layouts.ex:257 +#: lib/who_need_help_web/components/layouts.ex:266 #, elixir-autogen, elixir-format msgid "Account settings" msgstr "Настройки аккаунта" @@ -30,13 +30,13 @@ msgstr "Действия" msgid "Ask for help" msgstr "Попросить о помощи" -#: lib/who_need_help_web/components/layouts.ex:295 -#: lib/who_need_help_web/components/layouts.ex:310 +#: lib/who_need_help_web/components/layouts.ex:304 +#: lib/who_need_help_web/components/layouts.ex:319 #, elixir-autogen, elixir-format msgid "Attempting to reconnect" msgstr "Пытаемся восстановить соединение" -#: lib/who_need_help_web/components/layouts.ex:171 +#: lib/who_need_help_web/components/layouts.ex:175 #, elixir-autogen, elixir-format msgid "Categories" msgstr "Категории" @@ -65,13 +65,13 @@ msgid "Log in" msgstr "Войти" #: lib/who_need_help_web/components/layouts.ex:114 -#: lib/who_need_help_web/components/layouts.ex:260 +#: lib/who_need_help_web/components/layouts.ex:269 #, elixir-autogen, elixir-format msgid "Log out" msgstr "Выйти" #: lib/who_need_help_web/components/layouts.ex:108 -#: lib/who_need_help_web/components/layouts.ex:254 +#: lib/who_need_help_web/components/layouts.ex:263 #: lib/who_need_help_web/live/profile_live.ex:16 #, elixir-autogen, elixir-format msgid "Profile" @@ -83,7 +83,7 @@ msgstr "Профиль" msgid "Register" msgstr "Регистрация" -#: lib/who_need_help_web/components/layouts.ex:161 +#: lib/who_need_help_web/components/layouts.ex:165 #, elixir-autogen, elixir-format msgid "Requests" msgstr "Заявки" @@ -93,12 +93,12 @@ msgstr "Заявки" msgid "See nearby requests" msgstr "Заявки рядом" -#: lib/who_need_help_web/components/layouts.ex:302 +#: lib/who_need_help_web/components/layouts.ex:311 #, elixir-autogen, elixir-format msgid "Something went wrong!" msgstr "Что-то пошло не так!" -#: lib/who_need_help_web/components/layouts.ex:287 +#: lib/who_need_help_web/components/layouts.ex:296 #, elixir-autogen, elixir-format msgid "We can't find the internet" msgstr "Нет соединения с интернетом" @@ -179,7 +179,7 @@ msgid "Account Settings" msgstr "Настройки аккаунта" #: lib/who_need_help_web/components/layouts.ex:63 -#: lib/who_need_help_web/components/layouts.ex:192 +#: lib/who_need_help_web/components/layouts.ex:201 #, elixir-autogen, elixir-format msgid "Account and language" msgstr "Аккаунт и язык" @@ -200,7 +200,7 @@ msgstr "Центр действий" msgid "Active" msgstr "Активный" -#: lib/who_need_help_web/components/layouts.ex:166 +#: lib/who_need_help_web/components/layouts.ex:170 #: lib/who_need_help_web/live/activity_live/index.ex:118 #, elixir-autogen, elixir-format msgid "Activities" @@ -384,7 +384,7 @@ msgstr "" msgid "Briefly describe the help you need" msgstr "Кратко опишите, какая помощь вам нужна" -#: lib/who_need_help_web/components/layouts.ex:147 +#: lib/who_need_help_web/components/layouts.ex:151 #, elixir-autogen, elixir-format msgid "Build Week feedback" msgstr "Отчёт для Build Week" @@ -667,7 +667,7 @@ msgstr "" msgid "Dismissed" msgstr "Отклонено" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:76 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -691,7 +691,7 @@ msgstr "Нет аккаунта?" msgid "Double-blind review" msgstr "Двусторонний скрытый отзыв" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:67 #: 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 @@ -857,7 +857,7 @@ msgstr "Помощник" msgid "Helper: %{name}" msgstr "Помощник: %{name}" -#: lib/who_need_help_web/components/layouts.ex:176 +#: lib/who_need_help_web/components/layouts.ex:180 #, elixir-autogen, elixir-format msgid "Helpers" msgstr "Помощники" @@ -933,7 +933,7 @@ msgid "Human decisions retained" msgstr "Решения остаются за человеком" #: 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "Мне 18 лет и я принимаю правила безопасности." @@ -987,9 +987,12 @@ msgstr "" "Если этот адрес электронной почты есть в нашей системе, вскоре вы получите " "инструкции для входа." +#: lib/who_need_help_web/controllers/content_removal_html.ex:9 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:25 #: 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:1109 +#: lib/who_need_help_web/live/support_operations_live.ex:91 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "Выдача себя за другого человека" @@ -1212,7 +1215,7 @@ msgstr "Написать одобренной группе" msgid "Mode" msgstr "Режим" -#: lib/who_need_help_web/components/layouts.ex:181 +#: lib/who_need_help_web/components/layouts.ex:185 #: lib/who_need_help_web/live/moderation_live.ex:9 #: lib/who_need_help_web/live/moderation_live.ex:291 #, elixir-autogen, elixir-format @@ -1226,6 +1229,7 @@ msgid "Moderator" msgstr "Модератор" #: lib/who_need_help_web/live/moderation_live.ex:230 +#: lib/who_need_help_web/live/support_operations_live.ex:73 #: lib/who_need_help_web/user_auth.ex:306 #, elixir-autogen, elixir-format msgid "Moderator access is required." @@ -1370,12 +1374,16 @@ msgstr "" "Только организатор и одобренные участники могут читать или отправлять эти " "сообщения." +#: lib/who_need_help_web/controllers/content_removal_html.ex:21 +#: lib/who_need_help_web/controllers/support_html.ex:16 #: lib/who_need_help_web/live/activity_live/index.ex:166 #: 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:636 +#: lib/who_need_help_web/live/support_operations_live.ex:189 +#: lib/who_need_help_web/live/support_operations_live.ex:252 #, elixir-autogen, elixir-format msgid "Open" msgstr "Открыта" @@ -1420,10 +1428,17 @@ msgstr "Управление организатора" msgid "Organizer social links" msgstr "Ссылки организатора на соцсети" +#: lib/who_need_help_web/controllers/content_removal_html.ex:18 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:29 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:49 +#: lib/who_need_help_web/controllers/support_html.ex:13 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:35 #: 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:1110 +#: lib/who_need_help_web/live/support_operations_live.ex:85 +#: lib/who_need_help_web/live/support_operations_live.ex:100 #, elixir-autogen, elixir-format msgid "Other" msgstr "Другое" @@ -1474,6 +1489,7 @@ msgid "Please check the entered data." msgstr "Пожалуйста, проверьте введенные данные." #: lib/who_need_help_web/live/moderation_live.ex:240 +#: lib/who_need_help_web/live/support_operations_live.ex:75 #, elixir-autogen, elixir-format msgid "Please check the submitted fields." msgstr "Пожалуйста, проверьте представленные поля." @@ -1610,6 +1626,7 @@ msgid "READ BEFORE USING THE SERVICE" msgstr "ПРОЧИТАЙТЕ ПЕРЕД ИСПОЛЬЗОВАНИЕМ СЕРВИСА" #: lib/who_need_help_web/live/moderation_live.ex:290 +#: lib/who_need_help_web/live/support_operations_live.ex:111 #, elixir-autogen, elixir-format msgid "RESTRICTED WORKSPACE" msgstr "ОГРАНИЧЕННАЯ РАБОЧАЯ ОБЛАСТЬ" @@ -1634,11 +1651,12 @@ msgstr "" msgid "Rating" msgstr "Оценка" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:82 #, elixir-autogen, elixir-format msgid "Read the" msgstr "Прочитайте" +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:20 #: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" @@ -1752,8 +1770,10 @@ msgstr "Автор заявки: %{count} завершено" msgid "Resolution note" msgstr "Примечание к решению" +#: lib/who_need_help_web/controllers/support_html.ex:19 #: lib/who_need_help_web/live/moderation_live.ex:259 #: lib/who_need_help_web/live/moderation_live.ex:365 +#: lib/who_need_help_web/live/support_operations_live.ex:255 #, elixir-autogen, elixir-format msgid "Resolved" msgstr "Решено" @@ -1782,8 +1802,12 @@ msgstr "" msgid "Reviewed" msgstr "Проверено" +#: lib/who_need_help_web/controllers/content_removal_html.ex:23 +#: lib/who_need_help_web/controllers/support_html.ex:17 #: lib/who_need_help_web/live/moderation_live.ex:258 #: lib/who_need_help_web/live/moderation_live.ex:364 +#: lib/who_need_help_web/live/support_operations_live.ex:191 +#: lib/who_need_help_web/live/support_operations_live.ex:253 #, elixir-autogen, elixir-format msgid "Reviewing" msgstr "На рассмотрении" @@ -2269,12 +2293,12 @@ msgstr "" "адрес, код доступа, документ для поездки или другую чувствительную " "информацию." -#: lib/who_need_help_web/components/layouts.ex:349 +#: lib/who_need_help_web/components/layouts.ex:358 #, elixir-autogen, elixir-format msgid "Use dark theme" msgstr "Включить тёмную тему" -#: lib/who_need_help_web/components/layouts.ex:340 +#: lib/who_need_help_web/components/layouts.ex:349 #, elixir-autogen, elixir-format msgid "Use light theme" msgstr "Включить светлую тему" @@ -2292,7 +2316,7 @@ msgstr "" "Используйте приватный чат и, по желанию, передачу геопозиции в реальном " "времени." -#: lib/who_need_help_web/components/layouts.ex:331 +#: lib/who_need_help_web/components/layouts.ex:340 #, elixir-autogen, elixir-format msgid "Use system theme" msgstr "Использовать системную тему" @@ -2414,7 +2438,7 @@ msgstr "" msgid "Who Need Help home" msgstr "Главная Who Need Help" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 #, 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 "" @@ -2567,6 +2591,7 @@ msgstr "Ваш профиль" msgid "Your request is now visible to nearby helpers." msgstr "Теперь вашу заявку видят помощники рядом." +#: lib/who_need_help_web/controllers/support_html/index.html.heex:6 #: lib/who_need_help_web/live/request_live/index.ex:342 #, elixir-autogen, elixir-format msgid "Your requests" @@ -2593,7 +2618,7 @@ msgstr "активность" msgid "activity message" msgstr "сообщение активности" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:84 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "перед подтверждением." @@ -2670,7 +2695,7 @@ msgstr "оценка" msgid "rating %{rating}" msgstr "оценка %{rating}" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:83 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "Правила безопасности" @@ -2778,7 +2803,9 @@ msgstr "Объединено" msgid "Reciprocal rating ring" msgstr "Кольцо взаимных оценок" +#: lib/who_need_help_web/controllers/content_removal_html.ex:26 #: lib/who_need_help_web/live/moderation_live.ex:263 +#: lib/who_need_help_web/live/support_operations_live.ex:194 #, elixir-autogen, elixir-format msgid "Rejected" msgstr "Отклонено" @@ -2811,6 +2838,8 @@ msgstr "Загрузить предыдущие сообщения" #: lib/who_need_help_web/live/profile_live.ex:354 #: lib/who_need_help_web/live/request_live/index.ex:303 #: lib/who_need_help_web/live/request_live/index.ex:360 +#: lib/who_need_help_web/live/support_operations_live.ex:215 +#: lib/who_need_help_web/live/support_operations_live.ex:276 #, elixir-autogen, elixir-format msgid "Load more" msgstr "Загрузить ещё" @@ -2897,6 +2926,7 @@ msgid "The registration form is invalid." msgstr "Форма регистрации заполнена неверно." #: lib/who_need_help_web/live/moderation_live.ex:239 +#: lib/who_need_help_web/live/support_operations_live.ex:74 #, elixir-autogen, elixir-format msgid "The selected record is no longer available." msgstr "Выбранная запись больше недоступна." @@ -2996,7 +3026,7 @@ msgstr "Подключить аккаунт Google" 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:101 #, elixir-autogen, elixir-format msgid "Create account and email me a link" msgstr "Создать аккаунт и отправить ссылку на email" @@ -3058,7 +3088,7 @@ msgstr "Сессия входа через Google недействительна 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:100 #, elixir-autogen, elixir-format msgid "Sending link..." msgstr "Отправляем ссылку..." @@ -3142,3 +3172,632 @@ msgstr "Вход через Google отключён." #, elixir-autogen, elixir-format msgid "No Google account is connected." msgstr "Аккаунт Google не подключён." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:45 +#, elixir-autogen, elixir-format +msgid "A person I am authorised to represent" +msgstr "Лицо, которое я уполномочен представлять" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:8 +#, elixir-autogen, elixir-format +msgid "A private status link has been sent when a contact email was provided and delivery is configured. Opening that link verifies the contact address. Keep the reference for follow-up." +msgstr "Если указан контактный email и настроена доставка писем, на него отправлена приватная ссылка для проверки статуса. Переход по ссылке подтверждает адрес. Сохраните номер обращения." + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:3 +#, elixir-autogen, elixir-format +msgid "ACCOUNT AND DATA" +msgstr "АККАУНТ И ДАННЫЕ" + +#: lib/who_need_help_web/controllers/support_html.ex:6 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:29 +#: lib/who_need_help_web/live/support_operations_live.ex:78 +#, elixir-autogen, elixir-format +msgid "Account access" +msgstr "Доступ к аккаунту" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:115 +#, elixir-autogen, elixir-format +msgid "Account and personal data" +msgstr "Аккаунт и персональные данные" + +#: lib/who_need_help_web/controllers/support_html.ex:10 +#: lib/who_need_help_web/live/support_operations_live.ex:82 +#, elixir-autogen, elixir-format +msgid "Account deletion" +msgstr "Удаление аккаунта" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:26 +#, elixir-autogen, elixir-format +msgid "Account email" +msgstr "Email аккаунта" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:193 +#, elixir-autogen, elixir-format +msgid "Action taken" +msgstr "Приняты меры" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Additional information" +msgstr "Дополнительная информация" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:32 +#, elixir-autogen, elixir-format +msgid "Appeal a moderation decision" +msgstr "Обжаловать решение модерации" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Back to removal form" +msgstr "Вернуться к форме удаления" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Back to support" +msgstr "Вернуться в поддержку" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "CONTENT REMOVAL" +msgstr "УДАЛЕНИЕ КОНТЕНТА" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Check your email" +msgstr "Проверьте почту" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:27 +#, elixir-autogen, elixir-format +msgid "Choose a category" +msgstr "Выберите категорию" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:31 +#, elixir-autogen, elixir-format +msgid "Choose a reason" +msgstr "Выберите причину" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:51 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:47 +#, elixir-autogen, elixir-format +msgid "Choose a relationship" +msgstr "Выберите отношение" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:37 +#, elixir-autogen, elixir-format +msgid "Choose a topic" +msgstr "Выберите тему" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:27 +#: lib/who_need_help_web/controllers/support_html.ex:20 +#: lib/who_need_help_web/live/support_operations_live.ex:195 +#: lib/who_need_help_web/live/support_operations_live.ex:256 +#, elixir-autogen, elixir-format +msgid "Closed" +msgstr "Закрыто" + +#: lib/who_need_help_web/live/support_operations_live.ex:160 +#, elixir-autogen, elixir-format +msgid "Contact" +msgstr "Контакт" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Contact Who Need Help" +msgstr "Связаться с Who Need Help" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:38 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:34 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Contact email" +msgstr "Контактный email" + +#: lib/who_need_help_web/live/support_operations_live.ex:127 +#, elixir-autogen, elixir-format +msgid "Content removal and TAKE IT DOWN" +msgstr "Удаление контента и TAKE IT DOWN" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Content-removal notice" +msgstr "Уведомление об удалении контента" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:8 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:90 +#, elixir-autogen, elixir-format +msgid "Copyright" +msgstr "Авторские права" + +#: lib/who_need_help_web/live/support_operations_live.ex:76 +#, elixir-autogen, elixir-format +msgid "Could not update the request. Please try again." +msgstr "Не удалось обновить обращение. Попробуйте ещё раз." + +#: lib/who_need_help_web/controllers/support_html.ex:11 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:34 +#: lib/who_need_help_web/live/support_operations_live.ex:83 +#, elixir-autogen, elixir-format +msgid "Data export" +msgstr "Экспорт данных" + +#: lib/who_need_help_web/live/support_operations_live.ex:201 +#, elixir-autogen, elixir-format +msgid "Decision or information request" +msgstr "Решение или запрос дополнительной информации" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Decision or request for information" +msgstr "Решение или запрос дополнительной информации" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:19 +#, elixir-autogen, elixir-format +msgid "Delete an account" +msgstr "Удалить аккаунт" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:52 +#, elixir-autogen, elixir-format +msgid "Describe the problem" +msgstr "Опишите проблему" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:56 +#, elixir-autogen, elixir-format +msgid "Do not include passwords, payment card details, access codes, prescriptions, or unnecessary medical information." +msgstr "Не указывайте пароли, данные платёжных карт, коды доступа, рецепты или лишнюю медицинскую информацию." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Do not upload the image or video. Provide only the exact Who Need Help URL where it appears and enough text to identify it safely." +msgstr "Не загружайте изображение или видео. Укажите только точный URL в Who Need Help и безопасное текстовое описание, достаточное для идентификации материала." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:72 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Electronic signature (type your full name)" +msgstr "Электронная подпись (введите полное имя)" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:56 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Exact content URLs — one per line" +msgstr "Точные URL контента — по одному в строке" + +#: lib/who_need_help_web/live/support_operations_live.ex:220 +#, elixir-autogen, elixir-format +msgid "General support and data requests" +msgstr "Общая поддержка и запросы по данным" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "I am an authorised representative" +msgstr "Я уполномоченный представитель" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:47 +#, elixir-autogen, elixir-format +msgid "I am the affected person or rights holder" +msgstr "Я пострадавшее лицо или правообладатель" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:78 +#, elixir-autogen, elixir-format +msgid "I believe in good faith that the identified content is illegal or infringes the stated right." +msgstr "Я добросовестно считаю, что указанный контент незаконен или нарушает заявленное право." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:84 +#, elixir-autogen, elixir-format +msgid "I confirm that the information in this request is accurate and that I am the depicted person or authorised to act for them." +msgstr "Я подтверждаю точность сведений и то, что являюсь изображённым лицом либо уполномочен действовать от его имени." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:87 +#, elixir-autogen, elixir-format +msgid "I confirm that this notice is accurate and complete to the best of my knowledge." +msgstr "Я подтверждаю, что, насколько мне известно, это уведомление является точным и полным." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:75 +#, elixir-autogen, elixir-format +msgid "I have a good-faith belief that this intimate visual material was shared without consent." +msgstr "Я добросовестно считаю, что этот интимный визуальный материал был опубликован без согласия." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Identify the material without reproducing it and explain why it is non-consensual" +msgstr "Опишите материал, не воспроизводя его, и объясните, почему он размещён без согласия" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:93 +#, elixir-autogen, elixir-format +msgid "Identity and contact fields may be omitted for reports of sexual material involving a minor. Do not include or upload the material itself." +msgstr "При сообщении о сексуальном материале с участием несовершеннолетнего поля имени и контакта можно не заполнять. Не прикладывайте и не загружайте сам материал." + +#: lib/who_need_help_web/controllers/content_removal_html.ex:6 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:88 +#, elixir-autogen, elixir-format +msgid "Illegal content" +msgstr "Незаконный контент" + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:20 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. A copy is sent to the contact email when email delivery is configured." +msgstr "Не передавайте эту ссылку статуса другим. При настроенной доставке писем её копия отправляется на контактный email." + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. The submitter identity is not shown publicly or automatically disclosed to the person who posted the reported content." +msgstr "Не передавайте эту ссылку статуса другим. Личность заявителя не публикуется и не раскрывается автоматически автору обжалуемого контента." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Law, right, or policy involved, if known" +msgstr "Применимый закон, право или правило, если известно" + +#: lib/who_need_help_web/live/support_operations_live.ex:162 +#, elixir-autogen, elixir-format +msgid "Locations" +msgstr "Адреса контента" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:22 +#, elixir-autogen, elixir-format +msgid "Material involved" +msgstr "Тип материала" + +#: lib/who_need_help_web/controllers/support_html.ex:9 +#: lib/who_need_help_web/live/support_operations_live.ex:81 +#, elixir-autogen, elixir-format +msgid "Moderation appeal" +msgstr "Обжалование модерации" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:192 +#, elixir-autogen, elixir-format +msgid "More information needed" +msgstr "Нужна дополнительная информация" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:44 +#, elixir-autogen, elixir-format +msgid "Myself" +msgstr "Себя" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "NOTICE RECEIVED" +msgstr "УВЕДОМЛЕНИЕ ПОЛУЧЕНО" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:8 +#, elixir-autogen, elixir-format +msgid "New request" +msgstr "Новое обращение" + +#: lib/who_need_help_web/live/support_operations_live.ex:207 +#, elixir-autogen, elixir-format +msgid "No removal notices." +msgstr "Нет уведомлений об удалении." + +#: lib/who_need_help_web/live/support_operations_live.ex:268 +#, elixir-autogen, elixir-format +msgid "No support requests." +msgstr "Нет обращений в поддержку." + +#: lib/who_need_help_web/controllers/content_removal_html.ex:12 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:26 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:94 +#, elixir-autogen, elixir-format +msgid "Non-consensual intimate image or video" +msgstr "Интимное изображение или видео без согласия" + +#: lib/who_need_help_web/live/support_operations_live.ex:159 +#: lib/who_need_help_web/live/support_operations_live.ex:161 +#, elixir-autogen, elixir-format +msgid "Not provided" +msgstr "Не указано" + +#: lib/who_need_help_web/controllers/support_html.ex:12 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:33 +#: lib/who_need_help_web/live/support_operations_live.ex:84 +#, elixir-autogen, elixir-format +msgid "Privacy request" +msgstr "Запрос по конфиденциальности" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:7 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:23 +#: lib/who_need_help_web/live/support_operations_live.ex:89 +#, elixir-autogen, elixir-format +msgid "Privacy violation" +msgstr "Нарушение конфиденциальности" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Provide exact Who Need Help URLs. Do not upload or paste intimate imagery, identity documents, passwords, access codes, or unnecessary medical information." +msgstr "Укажите точные URL в Who Need Help. Не загружайте и не вставляйте интимные материалы, документы, удостоверяющие личность, пароли, коды доступа или лишнюю медицинскую информацию." + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "REQUEST RECEIVED" +msgstr "ОБРАЩЕНИЕ ПОЛУЧЕНО" + +#: lib/who_need_help_web/live/support_operations_live.ex:24 +#, elixir-autogen, elixir-format +msgid "Removal notice updated." +msgstr "Уведомление об удалении обновлено." + +#: lib/who_need_help_web/components/layouts.ex:149 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Report content" +msgstr "Пожаловаться" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Report illegal or harmful content" +msgstr "Сообщить о незаконном или вредоносном контенте" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:16 +#, elixir-autogen, elixir-format +msgid "Reported locations" +msgstr "Указанные адреса контента" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:4 +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:37 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:123 +#, elixir-autogen, elixir-format +msgid "Request account deletion" +msgstr "Запросить удаление аккаунта" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:117 +#, elixir-autogen, elixir-format +msgid "Request deletion of your account and associated personal data, or contact support about access and export requests." +msgstr "Запросите удаление аккаунта и связанных персональных данных либо обратитесь в поддержку по вопросам доступа и экспорта." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Request removal of intimate visual material" +msgstr "Запросить удаление интимного визуального материала" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:5 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "SUPPORT" +msgstr "ПОДДЕРЖКА" + +#: lib/who_need_help_web/controllers/support_html.ex:8 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:31 +#: lib/who_need_help_web/live/support_operations_live.ex:80 +#, elixir-autogen, elixir-format +msgid "Safety concern" +msgstr "Проблема безопасности" + +#: lib/who_need_help_web/live/support_operations_live.ex:203 +#: lib/who_need_help_web/live/support_operations_live.ex:264 +#, elixir-autogen, elixir-format +msgid "Save and notify" +msgstr "Сохранить и уведомить" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Send support request" +msgstr "Отправить обращение" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:15 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:27 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:97 +#, elixir-autogen, elixir-format +msgid "Sexual material involving a minor" +msgstr "Сексуальный материал с участием несовершеннолетнего" + +#: lib/who_need_help_web/live/support_operations_live.ex:177 +#, elixir-autogen, elixir-format +msgid "Stated basis:" +msgstr "Указанное основание:" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "Subject" +msgstr "Тема" + +#: lib/who_need_help_web/live/activity_live/show.ex:696 +#: lib/who_need_help_web/live/request_live/show.ex:1127 +#, elixir-autogen, elixir-format +msgid "Submit a legal content-removal notice" +msgstr "Подать юридическое уведомление об удалении контента" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:97 +#, elixir-autogen, elixir-format +msgid "Submit removal notice" +msgstr "Отправить уведомление об удалении" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:89 +#, elixir-autogen, elixir-format +msgid "Submit urgent removal request" +msgstr "Отправить срочный запрос на удаление" + +#: lib/who_need_help_web/live/support_operations_live.ex:158 +#, elixir-autogen, elixir-format +msgid "Submitter" +msgstr "Заявитель" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Submitting this form does not immediately sign you out or erase evidence required for an open safety investigation. Support will verify and process the request." +msgstr "Отправка формы не приводит к немедленному выходу из аккаунта и не удаляет доказательства, необходимые для открытого расследования безопасности. Поддержка проверит и обработает запрос." + +#: lib/who_need_help_web/components/layouts.ex:147 +#, elixir-autogen, elixir-format +msgid "Support" +msgstr "Поддержка" + +#: lib/who_need_help_web/live/support_operations_live.ex:8 +#: lib/who_need_help_web/live/support_operations_live.ex:114 +#, elixir-autogen, elixir-format +msgid "Support operations" +msgstr "Рабочее место поддержки" + +#: lib/who_need_help_web/components/layouts.ex:190 +#, elixir-autogen, elixir-format +msgid "Support queue" +msgstr "Очередь поддержки" + +#: lib/who_need_help_web/live/support_operations_live.ex:16 +#, elixir-autogen, elixir-format +msgid "Support request updated." +msgstr "Обращение в поддержку обновлено." + +#: lib/who_need_help_web/live/support_operations_live.ex:116 +#, elixir-autogen, elixir-format +msgid "Support requests and legal removal notices are separate queues. Decisions and operator identity are audited." +msgstr "Обращения в поддержку и юридические уведомления об удалении находятся в разных очередях. Решения и личность оператора фиксируются в аудите." + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:16 +#: lib/who_need_help_web/live/support_operations_live.ex:262 +#, elixir-autogen, elixir-format +msgid "Support response" +msgstr "Ответ поддержки" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:3 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN" +msgstr "TAKE IT DOWN" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:16 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN request" +msgstr "Запрос TAKE IT DOWN" + +#: lib/who_need_help_web/controllers/support_html.ex:7 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:30 +#: lib/who_need_help_web/live/support_operations_live.ex:79 +#, elixir-autogen, elixir-format +msgid "Technical issue" +msgstr "Техническая проблема" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6 +#, elixir-autogen, elixir-format +msgid "This requests deletion of your Who Need Help account and associated personal data. Some records may need to be retained when required for safety, fraud prevention, legal obligations, or resolving an active dispute; any retained data must be described in the applicable retention notice." +msgstr "Это запрос на удаление аккаунта Who Need Help и связанных персональных данных. Некоторые записи могут сохраняться, если это необходимо для безопасности, предотвращения мошенничества, выполнения закона или разрешения активного спора; сохранённые данные должны быть описаны в применимом уведомлении о хранении." + +#: lib/who_need_help_web/controllers/content_removal_html.ex:17 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:28 +#: lib/who_need_help_web/live/support_operations_live.ex:99 +#, elixir-autogen, elixir-format +msgid "Threat to life or safety" +msgstr "Угроза жизни или безопасности" + +#: lib/who_need_help_web/controllers/content_removal_controller.ex:54 +#, elixir-autogen, elixir-format +msgid "Too many removal notices. Please try again later." +msgstr "Слишком много уведомлений об удалении. Повторите попытку позже." + +#: lib/who_need_help_web/controllers/support_controller.ex:34 +#: lib/who_need_help_web/controllers/support_controller.ex:72 +#, elixir-autogen, elixir-format +msgid "Too many support requests. Please try again later." +msgstr "Слишком много обращений в поддержку. Повторите попытку позже." + +#: lib/who_need_help_web/live/support_operations_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Trust and safety moderation" +msgstr "Модерация доверия и безопасности" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:190 +#, elixir-autogen, elixir-format +msgid "Urgent review" +msgstr "Срочное рассмотрение" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:8 +#, elixir-autogen, elixir-format +msgid "Use this dedicated process when an identifiable person is shown in an intimate image or video shared without consent, including a digital forgery. You may submit for yourself or as an authorised representative." +msgstr "Используйте эту специальную процедуру, если узнаваемый человек показан на интимном изображении или видео, опубликованном без согласия, включая цифровую подделку. Запрос можно подать за себя или как уполномоченный представитель." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this electronic notice for specific content on Who Need Help that you believe is illegal or infringes your rights. Ordinary safety reports inside a request or chat remain available to signed-in users." +msgstr "Используйте это электронное уведомление для конкретного контента в Who Need Help, который вы считаете незаконным или нарушающим ваши права. Обычные жалобы на безопасность внутри заявки или чата доступны вошедшим пользователям." + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this form for account access, technical problems, privacy questions, or other support. For illegal content or a removal request, use the dedicated removal form." +msgstr "Используйте эту форму для вопросов доступа к аккаунту, технических проблем, конфиденциальности и другой поддержки. Для незаконного контента или запроса на удаление используйте специальную форму." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:126 +#, elixir-autogen, elixir-format +msgid "View support requests" +msgstr "Посмотреть обращения в поддержку" + +#: lib/who_need_help_web/live/support_operations_live.ex:254 +#, elixir-autogen, elixir-format +msgid "Waiting for requester" +msgstr "Ожидается ответ заявителя" + +#: lib/who_need_help_web/controllers/support_html.ex:18 +#, elixir-autogen, elixir-format +msgid "Waiting for your response" +msgstr "Ожидается ваш ответ" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:6 +#, elixir-autogen, elixir-format +msgid "We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests." +msgstr "Мы создали обращение %{reference}. При настроенной доставке писем приватная ссылка статуса отправлена на контактный email. Переход по ней подтверждает адрес для публичных обращений." + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:27 +#, elixir-autogen, elixir-format +msgid "What do you need help with?" +msgstr "Какая помощь вам нужна?" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:42 +#, elixir-autogen, elixir-format +msgid "Who are you submitting for?" +msgstr "От чьего имени вы подаёте запрос?" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:62 +#, elixir-autogen, elixir-format +msgid "Why do you believe this content should be removed?" +msgstr "Почему вы считаете, что этот контент следует удалить?" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:26 +#, elixir-autogen, elixir-format +msgid "You have not submitted any support requests." +msgstr "Вы ещё не отправляли обращений в поддержку." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Your full name" +msgstr "Ваше полное имя" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Your name or organisation" +msgstr "Ваше имя или организация" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Your reference is %{reference}" +msgstr "Номер вашего обращения: %{reference}" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:45 +#, elixir-autogen, elixir-format +msgid "Your relationship to the affected person or rights holder" +msgstr "Ваше отношение к пострадавшему лицу или правообладателю" + +#: lib/who_need_help_web/live/support_operations_live.ex:150 +#: lib/who_need_help_web/live/support_operations_live.ex:235 +#, elixir-autogen, elixir-format +msgid "contact unverified" +msgstr "контакт не подтверждён" + +#: lib/who_need_help_web/live/support_operations_live.ex:154 +#, elixir-autogen, elixir-format +msgid "review by %{time}" +msgstr "рассмотреть до %{time}" + +#: lib/who_need_help_web/controllers/support_controller.ex:47 +#: lib/who_need_help_web/controllers/support_controller.ex:58 +#, elixir-autogen, elixir-format +msgid "Delete my Who Need Help account" +msgstr "Удалить мой аккаунт Who Need Help" + +#: lib/who_need_help_web/controllers/support_controller.ex:48 +#, elixir-autogen, elixir-format +msgid "Please delete my account and associated personal data." +msgstr "Пожалуйста, удалите мой аккаунт и связанные персональные данные." diff --git a/priv/gettext/uk/LC_MESSAGES/default.po b/priv/gettext/uk/LC_MESSAGES/default.po index 6609371..068182f 100644 --- a/priv/gettext/uk/LC_MESSAGES/default.po +++ b/priv/gettext/uk/LC_MESSAGES/default.po @@ -12,7 +12,7 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100 != 11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n" #: lib/who_need_help_web/components/layouts.ex:111 -#: lib/who_need_help_web/components/layouts.ex:257 +#: lib/who_need_help_web/components/layouts.ex:266 #, elixir-autogen, elixir-format msgid "Account settings" msgstr "Параметри облікового запису" @@ -30,13 +30,13 @@ msgstr "Дії" msgid "Ask for help" msgstr "Попросити допомоги" -#: lib/who_need_help_web/components/layouts.ex:295 -#: lib/who_need_help_web/components/layouts.ex:310 +#: lib/who_need_help_web/components/layouts.ex:304 +#: lib/who_need_help_web/components/layouts.ex:319 #, elixir-autogen, elixir-format msgid "Attempting to reconnect" msgstr "Спроба повторного з’єднання" -#: lib/who_need_help_web/components/layouts.ex:171 +#: lib/who_need_help_web/components/layouts.ex:175 #, elixir-autogen, elixir-format msgid "Categories" msgstr "Категорії" @@ -65,13 +65,13 @@ msgid "Log in" msgstr "Вхід" #: lib/who_need_help_web/components/layouts.ex:114 -#: lib/who_need_help_web/components/layouts.ex:260 +#: lib/who_need_help_web/components/layouts.ex:269 #, elixir-autogen, elixir-format msgid "Log out" msgstr "Вийти" #: lib/who_need_help_web/components/layouts.ex:108 -#: lib/who_need_help_web/components/layouts.ex:254 +#: lib/who_need_help_web/components/layouts.ex:263 #: lib/who_need_help_web/live/profile_live.ex:16 #, elixir-autogen, elixir-format msgid "Profile" @@ -83,7 +83,7 @@ msgstr "Профіль" msgid "Register" msgstr "Зареєструватися" -#: lib/who_need_help_web/components/layouts.ex:161 +#: lib/who_need_help_web/components/layouts.ex:165 #, elixir-autogen, elixir-format msgid "Requests" msgstr "Запити" @@ -93,12 +93,12 @@ msgstr "Запити" msgid "See nearby requests" msgstr "Переглянути заявки поруч" -#: lib/who_need_help_web/components/layouts.ex:302 +#: lib/who_need_help_web/components/layouts.ex:311 #, elixir-autogen, elixir-format msgid "Something went wrong!" msgstr "Щось пішло не так!" -#: lib/who_need_help_web/components/layouts.ex:287 +#: lib/who_need_help_web/components/layouts.ex:296 #, elixir-autogen, elixir-format msgid "We can't find the internet" msgstr "Немає з’єднання з інтернетом" @@ -179,7 +179,7 @@ msgid "Account Settings" msgstr "Параметри облікового запису" #: lib/who_need_help_web/components/layouts.ex:63 -#: lib/who_need_help_web/components/layouts.ex:192 +#: lib/who_need_help_web/components/layouts.ex:201 #, elixir-autogen, elixir-format msgid "Account and language" msgstr "Акаунт і мова" @@ -200,7 +200,7 @@ msgstr "Центр дій" msgid "Active" msgstr "Активний" -#: lib/who_need_help_web/components/layouts.ex:166 +#: lib/who_need_help_web/components/layouts.ex:170 #: lib/who_need_help_web/live/activity_live/index.ex:118 #, elixir-autogen, elixir-format msgid "Activities" @@ -385,7 +385,7 @@ msgstr "" msgid "Briefly describe the help you need" msgstr "Коротко опишіть, яка допомога вам потрібна" -#: lib/who_need_help_web/components/layouts.ex:147 +#: lib/who_need_help_web/components/layouts.ex:151 #, elixir-autogen, elixir-format msgid "Build Week feedback" msgstr "Звіт для Build Week" @@ -668,7 +668,7 @@ msgstr "" msgid "Dismissed" msgstr "Відхилено" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:71 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:76 #: lib/who_need_help_web/live/profile_live.ex:149 #, elixir-autogen, elixir-format msgid "Display name" @@ -692,7 +692,7 @@ msgstr "Немає акаунта?" msgid "Double-blind review" msgstr "Двосторонній прихований відгук" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:62 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:67 #: 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 @@ -857,7 +857,7 @@ msgstr "Помічник" msgid "Helper: %{name}" msgstr "Помічник: %{name}" -#: lib/who_need_help_web/components/layouts.ex:176 +#: lib/who_need_help_web/components/layouts.ex:180 #, elixir-autogen, elixir-format msgid "Helpers" msgstr "Помічники" @@ -933,7 +933,7 @@ msgid "Human decisions retained" msgstr "Рішення залишаються за людиною" #: 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 #, elixir-autogen, elixir-format msgid "I am 18 or older and accept the safety rules" msgstr "Мені 18 років, і я приймаю правила безпеки." @@ -985,9 +985,12 @@ msgstr "" "Якщо ця адреса електронної пошти є в нашій системі, незабаром ви отримаєте " "інструкції для входу." +#: lib/who_need_help_web/controllers/content_removal_html.ex:9 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:25 #: 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:1109 +#: lib/who_need_help_web/live/support_operations_live.ex:91 #, elixir-autogen, elixir-format msgid "Impersonation" msgstr "Видавання себе за іншу особу" @@ -1208,7 +1211,7 @@ msgstr "Надіслати повідомлення схваленій груп msgid "Mode" msgstr "Режим" -#: lib/who_need_help_web/components/layouts.ex:181 +#: lib/who_need_help_web/components/layouts.ex:185 #: lib/who_need_help_web/live/moderation_live.ex:9 #: lib/who_need_help_web/live/moderation_live.ex:291 #, elixir-autogen, elixir-format @@ -1222,6 +1225,7 @@ msgid "Moderator" msgstr "Модератор" #: lib/who_need_help_web/live/moderation_live.ex:230 +#: lib/who_need_help_web/live/support_operations_live.ex:73 #: lib/who_need_help_web/user_auth.ex:306 #, elixir-autogen, elixir-format msgid "Moderator access is required." @@ -1365,12 +1369,16 @@ msgstr "" "Тільки організатор і схвалені учасники можуть читати або надсилати ці " "повідомлення." +#: lib/who_need_help_web/controllers/content_removal_html.ex:21 +#: lib/who_need_help_web/controllers/support_html.ex:16 #: lib/who_need_help_web/live/activity_live/index.ex:166 #: 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:636 +#: lib/who_need_help_web/live/support_operations_live.ex:189 +#: lib/who_need_help_web/live/support_operations_live.ex:252 #, elixir-autogen, elixir-format msgid "Open" msgstr "Відкрита" @@ -1415,10 +1423,17 @@ msgstr "Керування організатора" msgid "Organizer social links" msgstr "Посилання організатора на соцмережі" +#: lib/who_need_help_web/controllers/content_removal_html.ex:18 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:29 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:49 +#: lib/who_need_help_web/controllers/support_html.ex:13 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:35 #: 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:1110 +#: lib/who_need_help_web/live/support_operations_live.ex:85 +#: lib/who_need_help_web/live/support_operations_live.ex:100 #, elixir-autogen, elixir-format msgid "Other" msgstr "Інше" @@ -1469,6 +1484,7 @@ msgid "Please check the entered data." msgstr "Будь ласка, перевірте введені дані." #: lib/who_need_help_web/live/moderation_live.ex:240 +#: lib/who_need_help_web/live/support_operations_live.ex:75 #, elixir-autogen, elixir-format msgid "Please check the submitted fields." msgstr "Будь ласка, перевірте вказані поля." @@ -1605,6 +1621,7 @@ msgid "READ BEFORE USING THE SERVICE" msgstr "ПРОЧИТАЙТЕ ПЕРЕД ВИКОРИСТАННЯМ СЕРВІСУ" #: lib/who_need_help_web/live/moderation_live.ex:290 +#: lib/who_need_help_web/live/support_operations_live.ex:111 #, elixir-autogen, elixir-format msgid "RESTRICTED WORKSPACE" msgstr "ОБМЕЖЕНА РОБОЧА ОБЛАСТЬ" @@ -1628,11 +1645,12 @@ msgstr "" msgid "Rating" msgstr "Оцінка" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:77 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:82 #, elixir-autogen, elixir-format msgid "Read the" msgstr "Прочитайте" +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:20 #: lib/who_need_help_web/live/activity_live/show.ex:672 #, elixir-autogen, elixir-format msgid "Reason" @@ -1746,8 +1764,10 @@ msgstr "Автор заявки: %{count} завершено" msgid "Resolution note" msgstr "Примітка до рішення" +#: lib/who_need_help_web/controllers/support_html.ex:19 #: lib/who_need_help_web/live/moderation_live.ex:259 #: lib/who_need_help_web/live/moderation_live.ex:365 +#: lib/who_need_help_web/live/support_operations_live.ex:255 #, elixir-autogen, elixir-format msgid "Resolved" msgstr "Вирішено" @@ -1777,8 +1797,12 @@ msgstr "" msgid "Reviewed" msgstr "Перевірено" +#: lib/who_need_help_web/controllers/content_removal_html.ex:23 +#: lib/who_need_help_web/controllers/support_html.ex:17 #: lib/who_need_help_web/live/moderation_live.ex:258 #: lib/who_need_help_web/live/moderation_live.ex:364 +#: lib/who_need_help_web/live/support_operations_live.ex:191 +#: lib/who_need_help_web/live/support_operations_live.ex:253 #, elixir-autogen, elixir-format msgid "Reviewing" msgstr "На розгляді" @@ -2263,12 +2287,12 @@ msgstr "" "Призначайте першу зустріч у громадському місці. Не публікуйте домашню " "адресу, код доступу, документ для подорожі чи іншу чутливу інформацію." -#: lib/who_need_help_web/components/layouts.ex:349 +#: lib/who_need_help_web/components/layouts.ex:358 #, elixir-autogen, elixir-format msgid "Use dark theme" msgstr "Увімкнути темну тему" -#: lib/who_need_help_web/components/layouts.ex:340 +#: lib/who_need_help_web/components/layouts.ex:349 #, elixir-autogen, elixir-format msgid "Use light theme" msgstr "Увімкнути світлу тему" @@ -2286,7 +2310,7 @@ msgstr "" "Користуйтеся приватним чатом і, за бажанням, передаванням геопозиції в " "реальному часі." -#: lib/who_need_help_web/components/layouts.ex:331 +#: lib/who_need_help_web/components/layouts.ex:340 #, elixir-autogen, elixir-format msgid "Use system theme" msgstr "Використати системну тему" @@ -2408,7 +2432,7 @@ msgstr "" msgid "Who Need Help home" msgstr "Головна Who Need Help" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:90 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:95 #, 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 "" @@ -2557,6 +2581,7 @@ msgstr "Ваш профіль" msgid "Your request is now visible to nearby helpers." msgstr "Тепер вашу заявку бачать помічники поруч." +#: lib/who_need_help_web/controllers/support_html/index.html.heex:6 #: lib/who_need_help_web/live/request_live/index.ex:342 #, elixir-autogen, elixir-format msgid "Your requests" @@ -2583,7 +2608,7 @@ msgstr "активність" msgid "activity message" msgstr "повідомлення активності" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:79 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:84 #, elixir-autogen, elixir-format msgid "before confirming." msgstr "перед підтвердженням." @@ -2660,7 +2685,7 @@ msgstr "оцінка" msgid "rating %{rating}" msgstr "оцінка %{rating}" -#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:78 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:83 #, elixir-autogen, elixir-format msgid "safety rules" msgstr "правила безпеки" @@ -2768,7 +2793,9 @@ msgstr "Об’єднано" msgid "Reciprocal rating ring" msgstr "Кільце взаємних оцінок" +#: lib/who_need_help_web/controllers/content_removal_html.ex:26 #: lib/who_need_help_web/live/moderation_live.ex:263 +#: lib/who_need_help_web/live/support_operations_live.ex:194 #, elixir-autogen, elixir-format msgid "Rejected" msgstr "Відхилено" @@ -2801,6 +2828,8 @@ msgstr "Завантажити попередні повідомлення" #: lib/who_need_help_web/live/profile_live.ex:354 #: lib/who_need_help_web/live/request_live/index.ex:303 #: lib/who_need_help_web/live/request_live/index.ex:360 +#: lib/who_need_help_web/live/support_operations_live.ex:215 +#: lib/who_need_help_web/live/support_operations_live.ex:276 #, elixir-autogen, elixir-format msgid "Load more" msgstr "Завантажити ще" @@ -2887,6 +2916,7 @@ msgid "The registration form is invalid." msgstr "Форму реєстрації заповнено неправильно." #: lib/who_need_help_web/live/moderation_live.ex:239 +#: lib/who_need_help_web/live/support_operations_live.ex:74 #, elixir-autogen, elixir-format msgid "The selected record is no longer available." msgstr "Вибраний запис більше недоступний." @@ -2986,7 +3016,7 @@ msgstr "Підключити обліковий запис Google" 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:101 #, elixir-autogen, elixir-format msgid "Create account and email me a link" msgstr "Створити обліковий запис і надіслати посилання на email" @@ -3048,7 +3078,7 @@ msgstr "Сесія входу через Google недійсна." 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 +#: lib/who_need_help_web/controllers/user_registration_html/new.html.heex:100 #, elixir-autogen, elixir-format msgid "Sending link..." msgstr "Надсилаємо посилання..." @@ -3132,3 +3162,632 @@ msgstr "Вхід через Google відключено." #, elixir-autogen, elixir-format msgid "No Google account is connected." msgstr "Обліковий запис Google не підключено." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:45 +#, elixir-autogen, elixir-format +msgid "A person I am authorised to represent" +msgstr "Особа, яку я уповноважений представляти" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:8 +#, elixir-autogen, elixir-format +msgid "A private status link has been sent when a contact email was provided and delivery is configured. Opening that link verifies the contact address. Keep the reference for follow-up." +msgstr "Якщо вказано контактний email і налаштовано доставку листів, на нього надіслано приватне посилання для перевірки статусу. Перехід за посиланням підтверджує адресу. Збережіть номер звернення." + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:3 +#, elixir-autogen, elixir-format +msgid "ACCOUNT AND DATA" +msgstr "ОБЛІКОВИЙ ЗАПИС І ДАНІ" + +#: lib/who_need_help_web/controllers/support_html.ex:6 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:29 +#: lib/who_need_help_web/live/support_operations_live.ex:78 +#, elixir-autogen, elixir-format +msgid "Account access" +msgstr "Доступ до облікового запису" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:115 +#, elixir-autogen, elixir-format +msgid "Account and personal data" +msgstr "Обліковий запис і персональні дані" + +#: lib/who_need_help_web/controllers/support_html.ex:10 +#: lib/who_need_help_web/live/support_operations_live.ex:82 +#, elixir-autogen, elixir-format +msgid "Account deletion" +msgstr "Видалення облікового запису" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:26 +#, elixir-autogen, elixir-format +msgid "Account email" +msgstr "Email облікового запису" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:193 +#, elixir-autogen, elixir-format +msgid "Action taken" +msgstr "Вжито заходів" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Additional information" +msgstr "Додаткова інформація" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:32 +#, elixir-autogen, elixir-format +msgid "Appeal a moderation decision" +msgstr "Оскаржити рішення модерації" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Back to removal form" +msgstr "Повернутися до форми видалення" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Back to support" +msgstr "Повернутися до підтримки" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "CONTENT REMOVAL" +msgstr "ВИДАЛЕННЯ КОНТЕНТУ" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Check your email" +msgstr "Перевірте пошту" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:27 +#, elixir-autogen, elixir-format +msgid "Choose a category" +msgstr "Виберіть категорію" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:31 +#, elixir-autogen, elixir-format +msgid "Choose a reason" +msgstr "Виберіть причину" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:51 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:47 +#, elixir-autogen, elixir-format +msgid "Choose a relationship" +msgstr "Виберіть відношення" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:37 +#, elixir-autogen, elixir-format +msgid "Choose a topic" +msgstr "Виберіть тему" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:27 +#: lib/who_need_help_web/controllers/support_html.ex:20 +#: lib/who_need_help_web/live/support_operations_live.ex:195 +#: lib/who_need_help_web/live/support_operations_live.ex:256 +#, elixir-autogen, elixir-format +msgid "Closed" +msgstr "Закрито" + +#: lib/who_need_help_web/live/support_operations_live.ex:160 +#, elixir-autogen, elixir-format +msgid "Contact" +msgstr "Контакт" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Contact Who Need Help" +msgstr "Зв’язатися з Who Need Help" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:38 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:34 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:43 +#, elixir-autogen, elixir-format +msgid "Contact email" +msgstr "Контактний email" + +#: lib/who_need_help_web/live/support_operations_live.ex:127 +#, elixir-autogen, elixir-format +msgid "Content removal and TAKE IT DOWN" +msgstr "Видалення контенту та TAKE IT DOWN" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Content-removal notice" +msgstr "Повідомлення про видалення контенту" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:8 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:90 +#, elixir-autogen, elixir-format +msgid "Copyright" +msgstr "Авторські права" + +#: lib/who_need_help_web/live/support_operations_live.ex:76 +#, elixir-autogen, elixir-format +msgid "Could not update the request. Please try again." +msgstr "Не вдалося оновити звернення. Спробуйте ще раз." + +#: lib/who_need_help_web/controllers/support_html.ex:11 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:34 +#: lib/who_need_help_web/live/support_operations_live.ex:83 +#, elixir-autogen, elixir-format +msgid "Data export" +msgstr "Експорт даних" + +#: lib/who_need_help_web/live/support_operations_live.ex:201 +#, elixir-autogen, elixir-format +msgid "Decision or information request" +msgstr "Рішення або запит додаткової інформації" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Decision or request for information" +msgstr "Рішення або запит додаткової інформації" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:19 +#, elixir-autogen, elixir-format +msgid "Delete an account" +msgstr "Видалити обліковий запис" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:52 +#, elixir-autogen, elixir-format +msgid "Describe the problem" +msgstr "Опишіть проблему" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:56 +#, elixir-autogen, elixir-format +msgid "Do not include passwords, payment card details, access codes, prescriptions, or unnecessary medical information." +msgstr "Не вказуйте паролі, дані платіжних карток, коди доступу, рецепти або зайву медичну інформацію." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Do not upload the image or video. Provide only the exact Who Need Help URL where it appears and enough text to identify it safely." +msgstr "Не завантажуйте зображення чи відео. Укажіть лише точний URL у Who Need Help і безпечний текстовий опис, достатній для ідентифікації матеріалу." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:72 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Electronic signature (type your full name)" +msgstr "Електронний підпис (введіть повне ім’я)" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:56 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:53 +#, elixir-autogen, elixir-format +msgid "Exact content URLs — one per line" +msgstr "Точні URL контенту — по одному в рядку" + +#: lib/who_need_help_web/live/support_operations_live.ex:220 +#, elixir-autogen, elixir-format +msgid "General support and data requests" +msgstr "Загальна підтримка та запити щодо даних" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "I am an authorised representative" +msgstr "Я уповноважений представник" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:47 +#, elixir-autogen, elixir-format +msgid "I am the affected person or rights holder" +msgstr "Я постраждала особа або правовласник" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:78 +#, elixir-autogen, elixir-format +msgid "I believe in good faith that the identified content is illegal or infringes the stated right." +msgstr "Я добросовісно вважаю, що зазначений контент є незаконним або порушує заявлене право." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:84 +#, elixir-autogen, elixir-format +msgid "I confirm that the information in this request is accurate and that I am the depicted person or authorised to act for them." +msgstr "Я підтверджую точність відомостей і те, що є зображеною особою або уповноважений діяти від її імені." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:87 +#, elixir-autogen, elixir-format +msgid "I confirm that this notice is accurate and complete to the best of my knowledge." +msgstr "Я підтверджую, що, наскільки мені відомо, це повідомлення є точним і повним." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:75 +#, elixir-autogen, elixir-format +msgid "I have a good-faith belief that this intimate visual material was shared without consent." +msgstr "Я добросовісно вважаю, що цей інтимний візуальний матеріал було поширено без згоди." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Identify the material without reproducing it and explain why it is non-consensual" +msgstr "Опишіть матеріал, не відтворюючи його, і поясніть, чому його поширено без згоди" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:93 +#, elixir-autogen, elixir-format +msgid "Identity and contact fields may be omitted for reports of sexual material involving a minor. Do not include or upload the material itself." +msgstr "У повідомленні про сексуальний матеріал за участю неповнолітнього поля імені й контакту можна не заповнювати. Не додавайте й не завантажуйте сам матеріал." + +#: lib/who_need_help_web/controllers/content_removal_html.ex:6 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:88 +#, elixir-autogen, elixir-format +msgid "Illegal content" +msgstr "Незаконний контент" + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:20 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. A copy is sent to the contact email when email delivery is configured." +msgstr "Не передавайте це посилання статусу іншим. Якщо доставку листів налаштовано, його копія надсилається на контактний email." + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Keep this status link private. The submitter identity is not shown publicly or automatically disclosed to the person who posted the reported content." +msgstr "Не передавайте це посилання статусу іншим. Особа заявника не публікується й автоматично не розкривається автору оскаржуваного контенту." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:68 +#, elixir-autogen, elixir-format +msgid "Law, right, or policy involved, if known" +msgstr "Застосовний закон, право або правило, якщо відомо" + +#: lib/who_need_help_web/live/support_operations_live.ex:162 +#, elixir-autogen, elixir-format +msgid "Locations" +msgstr "Адреси контенту" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:22 +#, elixir-autogen, elixir-format +msgid "Material involved" +msgstr "Тип матеріалу" + +#: lib/who_need_help_web/controllers/support_html.ex:9 +#: lib/who_need_help_web/live/support_operations_live.ex:81 +#, elixir-autogen, elixir-format +msgid "Moderation appeal" +msgstr "Оскарження модерації" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:192 +#, elixir-autogen, elixir-format +msgid "More information needed" +msgstr "Потрібна додаткова інформація" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:44 +#, elixir-autogen, elixir-format +msgid "Myself" +msgstr "Себе" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "NOTICE RECEIVED" +msgstr "ПОВІДОМЛЕННЯ ОТРИМАНО" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:8 +#, elixir-autogen, elixir-format +msgid "New request" +msgstr "Нове звернення" + +#: lib/who_need_help_web/live/support_operations_live.ex:207 +#, elixir-autogen, elixir-format +msgid "No removal notices." +msgstr "Немає повідомлень про видалення." + +#: lib/who_need_help_web/live/support_operations_live.ex:268 +#, elixir-autogen, elixir-format +msgid "No support requests." +msgstr "Немає звернень до підтримки." + +#: lib/who_need_help_web/controllers/content_removal_html.ex:12 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:26 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:24 +#: lib/who_need_help_web/live/support_operations_live.ex:94 +#, elixir-autogen, elixir-format +msgid "Non-consensual intimate image or video" +msgstr "Інтимне зображення або відео без згоди" + +#: lib/who_need_help_web/live/support_operations_live.ex:159 +#: lib/who_need_help_web/live/support_operations_live.ex:161 +#, elixir-autogen, elixir-format +msgid "Not provided" +msgstr "Не вказано" + +#: lib/who_need_help_web/controllers/support_html.ex:12 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:33 +#: lib/who_need_help_web/live/support_operations_live.ex:84 +#, elixir-autogen, elixir-format +msgid "Privacy request" +msgstr "Запит щодо конфіденційності" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:7 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:23 +#: lib/who_need_help_web/live/support_operations_live.ex:89 +#, elixir-autogen, elixir-format +msgid "Privacy violation" +msgstr "Порушення конфіденційності" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Provide exact Who Need Help URLs. Do not upload or paste intimate imagery, identity documents, passwords, access codes, or unnecessary medical information." +msgstr "Укажіть точні URL у Who Need Help. Не завантажуйте й не вставляйте інтимні матеріали, документи, що посвідчують особу, паролі, коди доступу або зайву медичну інформацію." + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:3 +#, elixir-autogen, elixir-format +msgid "REQUEST RECEIVED" +msgstr "ЗВЕРНЕННЯ ОТРИМАНО" + +#: lib/who_need_help_web/live/support_operations_live.ex:24 +#, elixir-autogen, elixir-format +msgid "Removal notice updated." +msgstr "Повідомлення про видалення оновлено." + +#: lib/who_need_help_web/components/layouts.ex:149 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:13 +#, elixir-autogen, elixir-format +msgid "Report content" +msgstr "Поскаржитися" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:4 +#, elixir-autogen, elixir-format +msgid "Report illegal or harmful content" +msgstr "Повідомити про незаконний або шкідливий контент" + +#: lib/who_need_help_web/controllers/content_removal_html/show.html.heex:16 +#, elixir-autogen, elixir-format +msgid "Reported locations" +msgstr "Указані адреси контенту" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:4 +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:37 +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:123 +#, elixir-autogen, elixir-format +msgid "Request account deletion" +msgstr "Запросити видалення облікового запису" + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:117 +#, elixir-autogen, elixir-format +msgid "Request deletion of your account and associated personal data, or contact support about access and export requests." +msgstr "Запросіть видалення облікового запису та пов’язаних персональних даних або зверніться до підтримки щодо доступу й експорту." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Request removal of intimate visual material" +msgstr "Запросити видалення інтимного візуального матеріалу" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:5 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:3 +#, elixir-autogen, elixir-format +msgid "SUPPORT" +msgstr "ПІДТРИМКА" + +#: lib/who_need_help_web/controllers/support_html.ex:8 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:31 +#: lib/who_need_help_web/live/support_operations_live.ex:80 +#, elixir-autogen, elixir-format +msgid "Safety concern" +msgstr "Проблема безпеки" + +#: lib/who_need_help_web/live/support_operations_live.ex:203 +#: lib/who_need_help_web/live/support_operations_live.ex:264 +#, elixir-autogen, elixir-format +msgid "Save and notify" +msgstr "Зберегти й повідомити" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:60 +#, elixir-autogen, elixir-format +msgid "Send support request" +msgstr "Надіслати звернення" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:15 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:27 +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:25 +#: lib/who_need_help_web/live/support_operations_live.ex:97 +#, elixir-autogen, elixir-format +msgid "Sexual material involving a minor" +msgstr "Сексуальний матеріал за участю неповнолітнього" + +#: lib/who_need_help_web/live/support_operations_live.ex:177 +#, elixir-autogen, elixir-format +msgid "Stated basis:" +msgstr "Зазначена підстава:" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:48 +#, elixir-autogen, elixir-format +msgid "Subject" +msgstr "Тема" + +#: lib/who_need_help_web/live/activity_live/show.ex:696 +#: lib/who_need_help_web/live/request_live/show.ex:1127 +#, elixir-autogen, elixir-format +msgid "Submit a legal content-removal notice" +msgstr "Подати юридичне повідомлення про видалення контенту" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:97 +#, elixir-autogen, elixir-format +msgid "Submit removal notice" +msgstr "Надіслати повідомлення про видалення" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:89 +#, elixir-autogen, elixir-format +msgid "Submit urgent removal request" +msgstr "Надіслати терміновий запит на видалення" + +#: lib/who_need_help_web/live/support_operations_live.ex:158 +#, elixir-autogen, elixir-format +msgid "Submitter" +msgstr "Заявник" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:11 +#, elixir-autogen, elixir-format +msgid "Submitting this form does not immediately sign you out or erase evidence required for an open safety investigation. Support will verify and process the request." +msgstr "Надсилання форми не призводить до негайного виходу з облікового запису й не видаляє докази, потрібні для відкритого розслідування безпеки. Підтримка перевірить та опрацює запит." + +#: lib/who_need_help_web/components/layouts.ex:147 +#, elixir-autogen, elixir-format +msgid "Support" +msgstr "Підтримка" + +#: lib/who_need_help_web/live/support_operations_live.ex:8 +#: lib/who_need_help_web/live/support_operations_live.ex:114 +#, elixir-autogen, elixir-format +msgid "Support operations" +msgstr "Робоче місце підтримки" + +#: lib/who_need_help_web/components/layouts.ex:190 +#, elixir-autogen, elixir-format +msgid "Support queue" +msgstr "Черга підтримки" + +#: lib/who_need_help_web/live/support_operations_live.ex:16 +#, elixir-autogen, elixir-format +msgid "Support request updated." +msgstr "Звернення до підтримки оновлено." + +#: lib/who_need_help_web/live/support_operations_live.ex:116 +#, elixir-autogen, elixir-format +msgid "Support requests and legal removal notices are separate queues. Decisions and operator identity are audited." +msgstr "Звернення до підтримки та юридичні повідомлення про видалення перебувають у різних чергах. Рішення й особа оператора фіксуються в аудиті." + +#: lib/who_need_help_web/controllers/support_html/show.html.heex:16 +#: lib/who_need_help_web/live/support_operations_live.ex:262 +#, elixir-autogen, elixir-format +msgid "Support response" +msgstr "Відповідь підтримки" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:3 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN" +msgstr "TAKE IT DOWN" + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:16 +#, elixir-autogen, elixir-format +msgid "TAKE IT DOWN request" +msgstr "Запит TAKE IT DOWN" + +#: lib/who_need_help_web/controllers/support_html.ex:7 +#: lib/who_need_help_web/controllers/support_html/new.html.heex:30 +#: lib/who_need_help_web/live/support_operations_live.ex:79 +#, elixir-autogen, elixir-format +msgid "Technical issue" +msgstr "Технічна проблема" + +#: lib/who_need_help_web/controllers/support_html/delete_account.html.heex:6 +#, elixir-autogen, elixir-format +msgid "This requests deletion of your Who Need Help account and associated personal data. Some records may need to be retained when required for safety, fraud prevention, legal obligations, or resolving an active dispute; any retained data must be described in the applicable retention notice." +msgstr "Це запит на видалення облікового запису Who Need Help і пов’язаних персональних даних. Деякі записи можуть зберігатися, якщо це потрібно для безпеки, запобігання шахрайству, виконання закону або вирішення активного спору; збережені дані мають бути описані у відповідному повідомленні про зберігання." + +#: lib/who_need_help_web/controllers/content_removal_html.ex:17 +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:28 +#: lib/who_need_help_web/live/support_operations_live.ex:99 +#, elixir-autogen, elixir-format +msgid "Threat to life or safety" +msgstr "Загроза життю або безпеці" + +#: lib/who_need_help_web/controllers/content_removal_controller.ex:54 +#, elixir-autogen, elixir-format +msgid "Too many removal notices. Please try again later." +msgstr "Забагато повідомлень про видалення. Спробуйте пізніше." + +#: lib/who_need_help_web/controllers/support_controller.ex:34 +#: lib/who_need_help_web/controllers/support_controller.ex:72 +#, elixir-autogen, elixir-format +msgid "Too many support requests. Please try again later." +msgstr "Забагато звернень до підтримки. Спробуйте пізніше." + +#: lib/who_need_help_web/live/support_operations_live.ex:122 +#, elixir-autogen, elixir-format +msgid "Trust and safety moderation" +msgstr "Модерація довіри й безпеки" + +#: lib/who_need_help_web/controllers/content_removal_html.ex:22 +#: lib/who_need_help_web/live/support_operations_live.ex:190 +#, elixir-autogen, elixir-format +msgid "Urgent review" +msgstr "Терміновий розгляд" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:8 +#, elixir-autogen, elixir-format +msgid "Use this dedicated process when an identifiable person is shown in an intimate image or video shared without consent, including a digital forgery. You may submit for yourself or as an authorised representative." +msgstr "Скористайтеся цією спеціальною процедурою, якщо впізнавану людину показано на інтимному зображенні чи відео, поширеному без згоди, зокрема на цифровій підробці. Запит можна подати за себе або як уповноважений представник." + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this electronic notice for specific content on Who Need Help that you believe is illegal or infringes your rights. Ordinary safety reports inside a request or chat remain available to signed-in users." +msgstr "Скористайтеся цим електронним повідомленням для конкретного контенту в Who Need Help, який ви вважаєте незаконним або таким, що порушує ваші права. Звичайні скарги на безпеку всередині заявки чи чату доступні користувачам, які ввійшли." + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:6 +#, elixir-autogen, elixir-format +msgid "Use this form for account access, technical problems, privacy questions, or other support. For illegal content or a removal request, use the dedicated removal form." +msgstr "Скористайтеся цією формою для питань доступу до облікового запису, технічних проблем, конфіденційності та іншої підтримки. Для незаконного контенту або запиту на видалення скористайтеся спеціальною формою." + +#: lib/who_need_help_web/controllers/user_settings_html/edit.html.heex:126 +#, elixir-autogen, elixir-format +msgid "View support requests" +msgstr "Переглянути звернення до підтримки" + +#: lib/who_need_help_web/live/support_operations_live.ex:254 +#, elixir-autogen, elixir-format +msgid "Waiting for requester" +msgstr "Очікується відповідь заявника" + +#: lib/who_need_help_web/controllers/support_html.ex:18 +#, elixir-autogen, elixir-format +msgid "Waiting for your response" +msgstr "Очікується ваша відповідь" + +#: lib/who_need_help_web/controllers/support_html/received.html.heex:6 +#, elixir-autogen, elixir-format +msgid "We created support request %{reference}. A private status link has been sent to the contact email when delivery is configured. Opening that link verifies the address for public requests." +msgstr "Ми створили звернення %{reference}. Якщо доставку листів налаштовано, приватне посилання статусу надіслано на контактний email. Перехід за ним підтверджує адресу для публічних звернень." + +#: lib/who_need_help_web/controllers/support_html/new.html.heex:27 +#, elixir-autogen, elixir-format +msgid "What do you need help with?" +msgstr "Яка допомога вам потрібна?" + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:42 +#, elixir-autogen, elixir-format +msgid "Who are you submitting for?" +msgstr "Від чийого імені ви подаєте запит?" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:62 +#, elixir-autogen, elixir-format +msgid "Why do you believe this content should be removed?" +msgstr "Чому ви вважаєте, що цей контент слід видалити?" + +#: lib/who_need_help_web/controllers/support_html/index.html.heex:26 +#, elixir-autogen, elixir-format +msgid "You have not submitted any support requests." +msgstr "Ви ще не надсилали звернень до підтримки." + +#: lib/who_need_help_web/controllers/content_removal_html/take_it_down.html.heex:30 +#, elixir-autogen, elixir-format +msgid "Your full name" +msgstr "Ваше повне ім’я" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:34 +#, elixir-autogen, elixir-format +msgid "Your name or organisation" +msgstr "Ваше ім’я або організація" + +#: lib/who_need_help_web/controllers/content_removal_html/received.html.heex:5 +#, elixir-autogen, elixir-format +msgid "Your reference is %{reference}" +msgstr "Номер вашого звернення: %{reference}" + +#: lib/who_need_help_web/controllers/content_removal_html/new.html.heex:45 +#, elixir-autogen, elixir-format +msgid "Your relationship to the affected person or rights holder" +msgstr "Ваше відношення до постраждалої особи або правовласника" + +#: lib/who_need_help_web/live/support_operations_live.ex:150 +#: lib/who_need_help_web/live/support_operations_live.ex:235 +#, elixir-autogen, elixir-format +msgid "contact unverified" +msgstr "контакт не підтверджено" + +#: lib/who_need_help_web/live/support_operations_live.ex:154 +#, elixir-autogen, elixir-format +msgid "review by %{time}" +msgstr "розглянути до %{time}" + +#: lib/who_need_help_web/controllers/support_controller.ex:47 +#: lib/who_need_help_web/controllers/support_controller.ex:58 +#, elixir-autogen, elixir-format +msgid "Delete my Who Need Help account" +msgstr "Видалити мій обліковий запис Who Need Help" + +#: lib/who_need_help_web/controllers/support_controller.ex:48 +#, elixir-autogen, elixir-format +msgid "Please delete my account and associated personal data." +msgstr "Будь ласка, видаліть мій обліковий запис і пов’язані персональні дані." diff --git a/priv/repo/migrations/20260720231826_create_support_and_content_removal.exs b/priv/repo/migrations/20260720231826_create_support_and_content_removal.exs new file mode 100644 index 0000000..48cac47 --- /dev/null +++ b/priv/repo/migrations/20260720231826_create_support_and_content_removal.exs @@ -0,0 +1,58 @@ +defmodule WhoNeedHelp.Repo.Migrations.CreateSupportAndContentRemoval do + use Ecto.Migration + + def change do + create table(:support_requests, primary_key: false) do + add :id, :binary_id, primary_key: true + add :reference, :string, null: false + add :kind, :string, null: false + add :status, :string, null: false, default: "open" + add :contact_email, :string, null: false + add :subject, :string, null: false + add :details, :text, null: false + add :contact_verified_at, :utc_datetime + add :resolution_note, :text + add :reviewed_at, :utc_datetime + add :response_sent_at, :utc_datetime + add :requester_id, references(:users, type: :binary_id, on_delete: :nilify_all) + add :reviewed_by_id, references(:users, type: :binary_id, on_delete: :nilify_all) + timestamps(type: :utc_datetime) + end + + create unique_index(:support_requests, [:reference]) + create index(:support_requests, [:status, :inserted_at, :id]) + create index(:support_requests, [:kind, :status, :inserted_at, :id]) + create index(:support_requests, [:requester_id, :inserted_at, :id]) + + create table(:content_removal_notices, primary_key: false) do + add :id, :binary_id, primary_key: true + add :reference, :string, null: false + add :regime, :string, null: false, default: "general" + add :category, :string, null: false + add :status, :string, null: false, default: "open" + add :submitter_name, :string + add :contact_email, :string + add :relationship, :string + add :content_locations, :text, null: false + add :explanation, :text, null: false + add :legal_basis, :text + add :contact_verified_at, :utc_datetime + add :good_faith, :boolean, null: false, default: false + add :accurate_complete, :boolean, null: false, default: false + add :electronic_signature, :string + add :resolution_note, :text + add :reviewed_at, :utc_datetime + add :response_due_at, :utc_datetime + add :acknowledgement_sent_at, :utc_datetime + add :decision_sent_at, :utc_datetime + add :requester_id, references(:users, type: :binary_id, on_delete: :nilify_all) + add :reviewed_by_id, references(:users, type: :binary_id, on_delete: :nilify_all) + timestamps(type: :utc_datetime) + end + + create unique_index(:content_removal_notices, [:reference]) + create index(:content_removal_notices, [:status, :inserted_at, :id]) + create index(:content_removal_notices, [:regime, :status, :inserted_at, :id]) + create index(:content_removal_notices, [:requester_id, :inserted_at, :id]) + end +end diff --git a/scripts/init-production-env.sh b/scripts/init-production-env.sh index 0646d1c..28e4754 100755 --- a/scripts/init-production-env.sh +++ b/scripts/init-production-env.sh @@ -96,6 +96,7 @@ smtp_auth=${PRODUCTION_SMTP_AUTH:-always} smtp_tls=${PRODUCTION_SMTP_TLS:-always} smtp_ssl=${PRODUCTION_SMTP_SSL:-false} email_from_address=${PRODUCTION_EMAIL_FROM_ADDRESS:-"contact@$domain"} +support_inbox_address=${PRODUCTION_SUPPORT_INBOX_ADDRESS:-} tmp=$(mktemp "$target_dir/.production-env.XXXXXX") trap 'rm -f "$tmp"' EXIT HUP INT TERM @@ -123,6 +124,7 @@ SMTP_AUTH_VALUE=$smtp_auth \ SMTP_TLS_VALUE=$smtp_tls \ SMTP_SSL_VALUE=$smtp_ssl \ EMAIL_FROM_ADDRESS_VALUE=$email_from_address \ +SUPPORT_INBOX_ADDRESS_VALUE=$support_inbox_address \ awk ' BEGIN { replacement["DEPLOYMENT_TARGET"] = "compose" @@ -155,6 +157,7 @@ EMAIL_FROM_ADDRESS_VALUE=$email_from_address \ replacement["SMTP_TLS"] = ENVIRON["SMTP_TLS_VALUE"] replacement["SMTP_SSL"] = ENVIRON["SMTP_SSL_VALUE"] replacement["EMAIL_FROM_ADDRESS"] = ENVIRON["EMAIL_FROM_ADDRESS_VALUE"] + replacement["SUPPORT_INBOX_ADDRESS"] = ENVIRON["SUPPORT_INBOX_ADDRESS_VALUE"] replacement["CODEX_SESSION_ID"] = "not-configured" } { diff --git a/scripts/validate-production-env.sh b/scripts/validate-production-env.sh index 85222d2..c5531ee 100755 --- a/scripts/validate-production-env.sh +++ b/scripts/validate-production-env.sh @@ -97,6 +97,7 @@ 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) +support_inbox_address=$(optional_value SUPPORT_INBOX_ADDRESS) google_oauth_client_id=$(optional_value GOOGLE_OAUTH_CLIENT_ID) google_oauth_client_secret=$(optional_value GOOGLE_OAUTH_CLIENT_SECRET) @@ -224,6 +225,11 @@ fi echo "EMAIL_FROM_ADDRESS is not an email address." >&2 exit 1 } +if [[ -n "$support_inbox_address" && + ! "$support_inbox_address" =~ ^[^@,\;[:space:]]+@[^@,\;[:space:]]+$ ]]; then + echo "SUPPORT_INBOX_ADDRESS is not an email address." >&2 + exit 1 +fi if [[ -n "$google_oauth_client_id" || -n "$google_oauth_client_secret" ]]; then [[ -n "$google_oauth_client_id" && -n "$google_oauth_client_secret" ]] || { diff --git a/test/who_need_help/support_and_content_removal_test.exs b/test/who_need_help/support_and_content_removal_test.exs new file mode 100644 index 0000000..df067dd --- /dev/null +++ b/test/who_need_help/support_and_content_removal_test.exs @@ -0,0 +1,202 @@ +defmodule WhoNeedHelp.SupportAndContentRemovalTest do + use WhoNeedHelp.DataCase, async: false + + import WhoNeedHelp.AccountsFixtures + import Swoosh.TestAssertions + + alias WhoNeedHelp.{ContentRemoval, Repo, Support} + alias WhoNeedHelp.ContentRemoval.Notice + alias WhoNeedHelp.Support.SupportRequest + alias WhoNeedHelp.Trust.AuditEvent + + test "public support request is persisted, acknowledged, and verified only through its email link" do + assert {:ok, request} = + Support.create_request(nil, %{ + "kind" => "technical_issue", + "contact_email" => " Person@Example.com ", + "subject" => "Map does not open", + "details" => "The request map remains unavailable after reloading the page." + }) + + assert request.reference =~ "SUP-" + assert request.contact_email == "person@example.com" + assert is_nil(request.contact_verified_at) + + assert_email_sent(fn email -> + email.subject =~ request.reference and email.text_body =~ Support.status_url(request) + end) + + assert {:error, :not_found} = Support.get_by_access_token(request.id, "invalid") + + assert {:ok, verified} = + Support.get_by_access_token(request.id, Support.access_token(request)) + + assert verified.contact_verified_at + + assert Repo.exists?( + from event in AuditEvent, + where: + event.action == "support_request.created" and + event.target_id == ^request.id + ) + end + + test "configured support inbox receives a metadata-only operator alert and Reply-To" do + previous = Application.get_env(:who_need_help, :support_inbox_address) + Application.put_env(:who_need_help, :support_inbox_address, "support@example.com") + on_exit(fn -> Application.put_env(:who_need_help, :support_inbox_address, previous) end) + + assert {:ok, request} = + Support.create_request(nil, %{ + "kind" => "moderation_appeal", + "contact_email" => "appeal@example.com", + "subject" => "Appeal moderation decision", + "details" => + "Please review the moderation decision identified in my account notice." + }) + + assert_email_sent(fn email -> + email.to == [{"", "appeal@example.com"}] and + email.reply_to == {"", "support@example.com"} + end) + + assert_email_sent(fn email -> + email.to == [{"", "support@example.com"}] and email.subject =~ request.reference and + email.text_body =~ "/support/operations" and + not String.contains?(email.text_body, request.details) + end) + end + + test "authenticated support uses the account email and staff moderation is audited" do + user = user_fixture() + assert_email_sent() + scope = user_scope_fixture(user) + + assert {:ok, request} = + Support.create_request(scope, %{ + "kind" => "data_export", + "contact_email" => "forged@example.com", + "subject" => "Export my data", + "details" => + "Please prepare an export of the personal data associated with my account." + }) + + assert request.contact_email == user.email + assert request.contact_verified_at + assert_email_sent() + + moderator = + user_fixture() + |> Ecto.Changeset.change(role: :moderator) + |> Repo.update!() + + assert_email_sent() + + assert {:error, :forbidden} = + Support.moderate(scope, request.id, %{ + "status" => "resolved", + "resolution_note" => "Not allowed" + }) + + assert {:ok, resolved} = + Support.moderate(user_scope_fixture(moderator), request.id, %{ + "status" => "resolved", + "resolution_note" => "The export is ready for the verified account owner." + }) + + assert resolved.status == :resolved + assert resolved.response_sent_at + + assert_email_sent(fn email -> + email.subject =~ "support update" and email.text_body =~ "export is ready" + end) + end + + test "TAKE IT DOWN notices use a separate urgent queue and never accept an unrelated category" do + attrs = %{ + "category" => "non_consensual_intimate_media", + "submitter_name" => "Affected Person", + "contact_email" => "affected@example.com", + "relationship" => "self", + "content_locations" => "https://example.test/requests/one", + "explanation" => "An intimate visual depiction of me was posted without my consent.", + "electronic_signature" => "Affected Person", + "good_faith" => "true", + "accurate_complete" => "true" + } + + assert {:ok, notice} = ContentRemoval.create_notice(nil, :take_it_down, attrs) + assert notice.regime == :take_it_down + assert notice.status == :urgent_review + assert notice.response_due_at + assert is_nil(notice.contact_verified_at) + + assert_email_sent(fn email -> + email.subject =~ notice.reference and + email.text_body =~ "Do not reply with or upload intimate visual material" + end) + + assert {:error, changeset} = + ContentRemoval.create_notice(nil, :take_it_down, %{ + attrs + | "category" => "copyright", + "contact_email" => "copyright@example.com" + }) + + assert "must describe intimate visual material" in errors_on(changeset).category + end + + test "general notice validates exact URLs and permits anonymous child-safety reporting" do + assert {:error, changeset} = + ContentRemoval.create_notice(nil, :general, %{ + "category" => "illegal_content", + "submitter_name" => "Reporter", + "contact_email" => "reporter@example.com", + "relationship" => "other", + "content_locations" => "not a URL", + "explanation" => + "This is a sufficiently long explanation of the reported content.", + "electronic_signature" => "Reporter", + "good_faith" => "true", + "accurate_complete" => "true" + }) + + assert "must contain one full HTTP or HTTPS URL per line" in errors_on(changeset).content_locations + + assert {:ok, notice} = + ContentRemoval.create_notice(nil, :general, %{ + "category" => "child_sexual_abuse_material", + "content_locations" => "https://example.test/messages/safety-reference", + "explanation" => + "The referenced location appears to contain sexual material involving a minor.", + "good_faith" => "true", + "accurate_complete" => "true" + }) + + assert notice.status == :urgent_review + assert is_nil(notice.contact_email) + assert is_nil(notice.acknowledgement_sent_at) + assert [%Notice{id: id}] = ContentRemoval.paginate_for_staff(moderator_scope()).entries + assert id == notice.id + end + + test "account deletion is represented as a dedicated verified account-lifecycle request" do + user = user_fixture() + + assert {:ok, request} = + user + |> user_scope_fixture() + |> Support.create_account_deletion_request() + + assert %SupportRequest{kind: :account_deletion, requester_id: requester_id} = request + assert requester_id == user.id + assert request.contact_verified_at + end + + defp moderator_scope do + user_fixture() + |> Ecto.Changeset.change(role: :moderator) + |> Repo.update!() + |> user_scope_fixture() + end +end diff --git a/test/who_need_help_web/controllers/support_controller_test.exs b/test/who_need_help_web/controllers/support_controller_test.exs new file mode 100644 index 0000000..ca69253 --- /dev/null +++ b/test/who_need_help_web/controllers/support_controller_test.exs @@ -0,0 +1,158 @@ +defmodule WhoNeedHelpWeb.SupportControllerTest do + use WhoNeedHelpWeb.ConnCase, async: false + + import WhoNeedHelp.AccountsFixtures + import Swoosh.TestAssertions + + describe "public support and removal intake" do + test "renders each public form", %{conn: conn} do + assert html_response(get(conn, ~p"/support"), 200) =~ "Contact Who Need Help" + assert html_response(get(conn, ~p"/account/delete"), 200) =~ "Request account deletion" + + assert html_response(get(conn, ~p"/legal/content-removal"), 200) =~ + "Report illegal or harmful content" + + assert html_response( + get(conn, ~p"/legal/content-removal?location=/requests/specific-id"), + 200 + ) =~ "/requests/specific-id" + + assert html_response(get(conn, ~p"/legal/take-it-down"), 200) =~ + "Do not upload the image or video" + end + + test "creates support request without exposing its status token in the redirect", %{ + conn: conn + } do + conn = + post(conn, ~p"/support", %{ + "support_request" => %{ + "kind" => "technical_issue", + "contact_email" => "person@example.com", + "subject" => "Unable to open a request", + "details" => "The request page reports an error after the form is submitted." + } + }) + + location = redirected_to(conn) + assert location =~ "/support/received?reference=SUP-" + refute location =~ "token=" + assert_email_sent() + end + + test "creates urgent TAKE IT DOWN notice without accepting a media upload", %{conn: conn} do + conn = + post(conn, ~p"/legal/take-it-down", %{ + "notice" => %{ + "category" => "non_consensual_intimate_media", + "submitter_name" => "Affected Person", + "contact_email" => "affected@example.com", + "relationship" => "self", + "content_locations" => "https://example.test/requests/reported", + "explanation" => "The identified intimate image was posted without my consent.", + "electronic_signature" => "Affected Person", + "good_faith" => "true", + "accurate_complete" => "true" + } + }) + + location = redirected_to(conn) + assert location =~ "/legal/content-removal/received?reference=REM-" + refute location =~ "token=" + assert_email_sent() + end + + test "rejects malformed public form shapes without crashing", %{conn: conn} do + assert response(post(conn, ~p"/support", %{"support_request" => "invalid"}), 400) + + assert response( + post(conn, ~p"/legal/content-removal", %{"notice" => "invalid"}), + 400 + ) + + assert response( + post(conn, ~p"/legal/take-it-down", %{"notice" => "invalid"}), + 400 + ) + end + end + + describe "authenticated support" do + setup :register_and_log_in_user + + test "lists the current user's cases and links account deletion from settings", %{ + conn: conn, + user: user + } do + {:ok, request} = + WhoNeedHelp.Support.create_request(user_scope_fixture(user), %{ + "kind" => "account_access", + "subject" => "Account question", + "details" => "I need assistance understanding an account setting." + }) + + assert html_response(get(conn, ~p"/support/requests"), 200) =~ request.reference + assert html_response(get(conn, ~p"/users/settings"), 200) =~ ~p"/account/delete" + end + end + + describe "operator queue authorization" do + test "requires a moderator", %{conn: conn} do + conn = get(conn, ~p"/support/operations") + assert redirected_to(conn) == ~p"/users/log-in" + end + + test "renders separate support and removal queues for a moderator", %{conn: conn} do + moderator = + user_fixture() + |> Ecto.Changeset.change(role: :moderator) + |> WhoNeedHelp.Repo.update!() + + {:ok, _support_request} = + WhoNeedHelp.Support.create_request(nil, %{ + "kind" => "technical_issue", + "contact_email" => "support-queue@example.com", + "subject" => "Check the support queue", + "details" => "This request verifies that an open status remains selected." + }) + + {:ok, _removal_notice} = + WhoNeedHelp.ContentRemoval.create_notice(nil, :general, %{ + "category" => "illegal_content", + "submitter_name" => "Queue Reporter", + "contact_email" => "removal-queue@example.com", + "relationship" => "self", + "content_locations" => "https://example.test/requests/queue-check", + "explanation" => "This notice verifies that an open status remains selected.", + "electronic_signature" => "Queue Reporter", + "good_faith" => "true", + "accurate_complete" => "true" + }) + + response = + conn + |> log_in_user(moderator) + |> get(~p"/support/operations") + |> html_response(200) + + assert response =~ "Content removal and TAKE IT DOWN" + assert response =~ "General support and data requests" + + document = LazyHTML.from_document(response) + + assert Enum.count( + LazyHTML.query( + document, + "select[id^='removal-'] option[value='open'][selected]" + ) + ) == 1 + + assert Enum.count( + LazyHTML.query( + document, + "select[id^='support-'] option[value='open'][selected]" + ) + ) == 1 + end + end +end