# `MishkaCmsCore.Runtime.Resources.Validations.PreventMasterLanguageDeletion`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/resources/__validations/prevent_master_language_deletion.ex#L5)

Refuses to destroy the original edition of a translated record while translations of it are live.

A translated record is several rows sharing a `language_group_id`, and the group's key *is* the
original's own id — so the original is the row where `id == language_group_id`, or where there is
no group at all, and every other row in the group points at it. Destroying the original first would
leave those pointing at nothing, so this says no until they are gone. A translation itself is free
to go at any time.

It queries `changeset.resource`, so the same validation guards every resource that carries the
pattern — `MishkaCmsCore.Runtime.Page`, `MishkaBlog.Collection`, `MishkaBlog.Tag`,
`MishkaBlog.BlogPost`, `MishkaDocument.Workspace`, and anything added later.

Put it on every destroy action, the archiving ones and the permanent ones:

    destroy :destroy do
      validate MishkaCmsCore.Runtime.Resources.Validations.PreventMasterLanguageDeletion
    end

    destroy :permanent_destroy do
      validate MishkaCmsCore.Runtime.Resources.Validations.PreventMasterLanguageDeletion
    end

Both kinds wait for the same thing, a translation that is still live. An archived one is in nobody's
way: `MishkaCmsCore.Runtime.Resources.Changes.PreventOrphanedTranslationRestore` keeps it from
coming back under an original that is not there. To remove a whole group, destroy the translations
first, then the original.

It holds for every caller, `authorize?: false` included, and a check that cannot be answered
refuses too.

## Plugin event

Each refusal for live translations fires `runtime:language:delete_refused` with `%{resource, id,
site_id, language, translations, message}`: the original, its language, the languages of its live
translations, and the message the refusal carries.

## The refusal

An `Ash.Error.Changes.InvalidAttribute` on `:language_group_id` whose message is `message/2`: the
original's language, the languages still live, and what to do. The API returns that message as it
is.

The message is written in the Gettext locale of the process the action runs in, `errors` domain,
since the language names inside it are translated too. A request with no locale reads it in
English. Its `private_vars` hold `live_translations_of: id`, the original's id, which is how
`MishkaCmsCore.LanguageEditions.refused_originals/1` finds it among an action's errors.

# `message`

```elixir
@spec message(String.t() | atom() | nil, [String.t() | atom() | nil]) :: String.t()
```

What refusing an original says: its own language, the languages of its live translations, and
what to do.

    message("en", ["fr", "fa"])
    #=> "This is the original English version. Its translations in French and Persian are still
    #    live, so it can't be deleted yet. Archive or delete those translations first, then this one."

---

*Consult [api-reference.md](api-reference.md) for complete listing*
