# `PhoenixKit.Modules.Emails.TemplateExport`
[🔗](https://github.com/BeamLabEU/phoenix_kit_emails/blob/0.5.0/lib/phoenix_kit/modules/emails/template_export.ex#L1)

Decides which stored templates become override files, and what those files
are called.

Message templates are moving from `phoenix_kit_email_templates` to files a
host owns in its own repository. `plan/2` answers the two questions that
migration turns on — *which rows carry an edit worth keeping*, and *what
filename preserves the behaviour that row had* — as data, so
`mix phoenix_kit_emails.templates.export` is left with nothing to do but
write and report.

## Which rows

| Row | Planned | Why |
|---|---|---|
| System template, edited | **yes** | the edit is the thing worth keeping |
| System template, untouched | no | byte-identical to what this package ships; core supplies it now, translated |
| Operator-authored (`is_system: false`) | no | newsletter layouts, authored at runtime — they keep a table and an editor |

A template whose name this package does not ship counts as **edited**:
nothing is known about what it should look like, and a file too many is
recoverable where a dropped edit is not.

## Which filename

The name becomes a directory and each part a file. The subtle part is the
locale: a stored field is a language map, and `Template.get_translation/3`
falls back through it, so one locale's content is what every recipient
without their own translation actually receives. That locale must be written
**without** a code in the filename, because a locale-less file is what core's
resolution falls through to. Writing it as `subject.en.txt` would silently
drop the operator's customization for every non-English recipient — the exact
opposite of the point of exporting it.

# `plan`

```elixir
@type plan() :: %{
  edited: [PhoenixKit.Modules.Emails.Template.t()],
  untouched: [PhoenixKit.Modules.Emails.Template.t()],
  authored: [PhoenixKit.Modules.Emails.Template.t()],
  files: [{Path.t(), String.t()}]
}
```

What an export would do, without having done any of it.

# `edited?`

```elixir
@spec edited?(PhoenixKit.Modules.Emails.Template.t(), %{optional(String.t()) =&gt; map()}) ::
  boolean()
```

Whether `template` still matches what this package ships under its name.

Compares the three content fields only — a slug, a usage count or a
timestamp differing does not make a template customized.

# `fallback_locale`

```elixir
@spec fallback_locale(map()) :: String.t() | nil
```

The locale every recipient falls through to: `"en"` when the map has it —
the key `Template.get_translation/3` itself defaults to — otherwise the
lowest-sorting one, so the choice is deterministic rather than whatever the
map happens to yield first.

# `files_for`

```elixir
@spec files_for(PhoenixKit.Modules.Emails.Template.t(), Path.t()) :: [
  {Path.t(), String.t()}
]
```

The `{path, content}` pairs one template exports to.

Empty and non-string values are skipped: a part a template does not supply
is absent, not an empty file.

# `plan`

```elixir
@spec plan([PhoenixKit.Modules.Emails.Template.t()], [map()], keyword()) :: plan()
```

Plans an export of `templates` against `shipped` (this package's own
defaults, as `default_system_templates/0` returns them).

Options: `:out`, the target directory (default `priv/phoenix_kit_templates`).

# `write_files`

```elixir
@spec write_files(
  [{Path.t(), String.t()}],
  keyword()
) :: [{Path.t(), :written | :would_write | :skipped}]
```

Writes planned `{path, content}` pairs, returning `{path, outcome}` for each.

Outcomes are `:written`, `:would_write` (under `dry_run: true`) and
`:skipped` — a path that already exists is **refused** unless `force: true`.
That refusal is the point: a re-run, or an export onto a host that has
already hand-written an override, must never silently replace a file a human
wrote. Nothing here deletes.

---

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