# `MishkaCmsMigration.Targets.Helpers.Editions`
[🔗](https://github.com/mishka-group/mishka_cms_migration/blob/v0.0.1/lib/mishka_cms_migration/targets/__helpers/editions.ex#L5)

An item's language and the other editions of it, for the targets whose resource keeps one record
per language, joined by `language_group_id` (`MishkaCmsCore.LanguageEditions`): the core's pages
(`MishkaCmsMigration.Targets.CmsPage`) and the Blog's posts, categories and tags
(`MishkaCmsMigration.Targets.BlogPost`, `MishkaCmsMigration.Targets.BlogCollection`,
`MishkaCmsMigration.Targets.BlogTag`). A source names them in the item's `"language"` and
`"translations"` (`%{"en" => "121"}`, the source id of each other edition by language), as the
WordPress source reads them from Polylang or WPML (`MishkaCmsMigration.Sources.WordPress`,
"Languages").

## Its record

The CMS keeps a record in several languages as one main record, its master, and a translation
of it per other language: each a record of its own, in its language (`attrs/3`), whose
`language_group_id` is the master's id, the key the master's own `language_group_id` holds too
(`MishkaCmsCore.LanguageEditions`, `MishkaCmsCore.Runtime.Resources.Changes.SetLanguageGroupId`).
The CMS's Add translation writes a translation the same way: the resource's create, with the
master's group (`MishkaCmsCore.Runtime.PageEditions.translate/4`).

The master is the group's original: the edition in the site's main language, else the first by
code, since the CMS lets a record in any language lead its group. The original is written as a
group of its own, which the resource's create makes it, and every other edition joins the group
of the records its editions' items (`MishkaCmsMigration.MigrationItem`) were written to. An
original written after other editions of it, by a later page of the source or a later run, then
takes them over (`lead/4`): they, and any translation an admin added to them in the CMS, move to
its group through the resource's update action, and their items record the new `updated_at`, so
a later run does not take the move for an edit in the CMS. So the editions are one group, led by
the original, whatever order the source lists them in, and whichever of them an earlier run
wrote. That costs two queries for an item with editions, its editions' items and their records,
one more and an update each when the original takes them over, and none for one without.

## A language the site does not publish in

An item in a language that is not one of the site's (`MishkaCmsCore.Runtime.Languages.site/1`)
waits for an admin's decision on `"language_missing"`, its reason naming the code
(`"language_missing: fa"`), in a dry run (`waiting/2`) and in an import (`call/2`), for an item an
earlier import wrote as for a new one. Add the language to the site (`"import"`) adds it to the
end of the site's `site.languages` as the admin (`MishkaCmsCore.Settings.put_as/4`) before the
item is written, and the report says so (`"language_added: fa"`); the items in that language
written after it then wait on nothing. Leave the translation out leaves the item out.

## A language the source does not say

On a run whose source runs a language plugin (`MishkaCmsMigration.Target`'s context, `languages`),
an item that names no language waits for an admin's decision on `"language_unknown"`: writing it
in the site's language could mislabel a translation. Polylang's free plugin names an entry's
language over the REST API only by the address it serves it under, which the WordPress source
reads (`MishkaCmsMigration.Sources.WordPress`, "Languages"), so this waits only for an entry no
language's address holds. Import it in the site's main language (`"import"`) writes it so;
Wait (`"keep"`) skips it as `"waits_for_language"`, for a run of the site's export file, which
carries every entry's language. An item that names no language on a run of a source with no
language plugin is in the site's main language (`MishkaCmsCore.Runtime.Languages.primary/1`).

## One language for the run

A run whose New import window chose one language (`one_language` of its options,
`MishkaCmsMigration.MigrationRun.one_language/2`, one of the site's languages) asks neither
decision: an item that names no language, and one in a language the site does not publish in, is
written in that language and waits for nothing, and the Items page says so in one line. An item
in one of the site's languages keeps it, and the editions the source links stay linked.

## What the report says

`note/3`: `"translations: fa, de"` for the group's original, the edition in the site's main
language (else the first by code), naming its other editions, and `"translation_of: <title>
(<code>)"` for every other edition, with the original's title as the item an earlier run or
this one recorded it (`#<source id>` before any did), in a dry run and in an import; and
`"language_added: <code>"` once an admin decided to add the item's language.

# `attrs`

```elixir
@spec attrs(
  MishkaCmsMigration.Source.Item.t(),
  MishkaCmsMigration.Target.context(),
  Ash.Resource.t()
) ::
  map()
```

The language and group a target writes `item` with, to the records of `resource`: its
`language`, and its `language_group_id` when an edition of it was written already (see "Its
record"), which the input leaves out otherwise.

# `call`

```elixir
@spec call(MishkaCmsMigration.Source.Item.t(), MishkaCmsMigration.Target.context()) ::
  :ok | {:error, term()} | {:skipped, String.t()} | {:undecided, String.t()}
```

What a target's `prepare/3` asks first: `:ok` for an item in one of the site's languages, or one
an admin decided to import, after adding its language to the site; else `{:undecided, conflict}`,
`{:skipped, "waits_for_language"}`, or the core's refusal of the setting.

# `language`

```elixir
@spec language(
  MishkaCmsMigration.Source.Item.t(),
  MishkaCmsMigration.Target.context()
) :: String.t()
```

The language `item` is written in: its own, else the site's main language; on a run that puts
such items in one language (see "One language for the run"), that language for an item with none
or with one the site does not publish in.

# `lead`

```elixir
@spec lead(
  MishkaCmsMigration.Source.Item.t(),
  MishkaCmsMigration.Target.context(),
  module(),
  (MishkaCmsMigration.Target.context() -&gt; result)
) :: result | {:error, term()}
when result: term()
```

Runs `write`, the import's write of `item` by `target`, and, when `item` is the original of its
group (see "Its record"), moves the editions written before it into its group, as the admin,
with `target`'s update action. Answers what `write` does, or the CMS's refusal of a move.

# `note`

```elixir
@spec note(
  MishkaCmsMigration.Source.Item.t(),
  MishkaCmsMigration.Target.context(),
  String.t() | nil
) ::
  String.t() | nil
```

What the report says of `item`'s language and editions (see "What the report says"), followed by
`also`, a target's own note, joined by `"; "`, or `nil` when neither says anything.

# `prepare`

```elixir
@spec prepare(
  MishkaCmsMigration.Source.Item.t(),
  MishkaCmsMigration.Target.context(),
  module(),
  (MishkaCmsMigration.Target.context() -&gt; result)
) ::
  result | {:error, term()} | {:skipped, String.t()} | {:undecided, String.t()}
when result: term()
```

The `prepare/3` of `target`, a target whose write waits on nothing else
(`MishkaCmsMigration.Target`): `write.(context)`, led by `lead/4`, once `call/2` answers `:ok`,
else what it answers.

# `waiting`

```elixir
@spec waiting(MishkaCmsMigration.Source.Item.t(), MishkaCmsMigration.Target.context()) ::
  MishkaCmsMigration.Target.found() | nil
```

What a target's `waiting/2` answers in a dry run: what `call/2` would, without writing.

---

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