Changelog

View Source

0.2.0 - 2026-09-23

Breaking

  • html now HTML-escapes a bound {{variable}} value (& < > " ', the set Phoenix.HTML escapes). A value carrying markup — a user or company name — no longer becomes live HTML in an email. subject and text are plain text and are still never escaped.

    Upgrading: a host override whose html part relies on a variable carrying markup on purpose (billing's line_items_html, a wrapped content) must switch that placeholder from {{variable}} to {{{variable}}}. A value that was being pre-escaped by the caller to compensate is now escaped twice — stop escaping it, or opt it out with triple braces.

  • {{{variable}}} is now parsed in every part. In 0.1.x {{{x}}} rendered as {V} (literal outer braces around a substituted {{x}}); it now renders V. This applies to subject and text too, independent of escaping. Database templates in phoenix_kit_emails / phoenix_kit_newsletters still use their own {{var}}-only substitution, so a database row must not be written with triple braces.

Added

  • {{{variable}}} (triple braces) substitutes raw in every part — the opt-out for a variable that already holds rendered HTML. (#1)
  • Substitution.substitute/3 with an :escape option. Unknown options and a non-boolean :escape raise ArgumentError, so a typo like escaped: true cannot silently render unescaped.
  • Substitution.variables/1, missing/2 and Templates.missing_variables/4 report triple-brace placeholders like double-brace ones.

Changed

  • HTML escaping is a single pass over the value.

0.1.2 - 2026-09-16

Fixed

  • Invalid input no longer creates cache entries. Overrides.read/4 checks the name, part and locale before it touches :persistent_term. A malformed locale now shares the nil locale's cache entry, and an invalid name or unknown part returns nil without caching anything. Before, each of these added a permanent key, and every new key copies the whole table.
  • Overrides.read/4 now raises on a bare string root instead of quietly finding no overrides. The old behaviour also cached a key that made a later reset_cache/1 crash. render/4 treats paths: nil as [].
  • The hexdocs source links now point at the v<version> tag instead of a ref that doesn't exist.
  • The README and moduledoc example called gettext("Hi %{email}, …") without bindings, which raises. It now uses a {{user_email}} placeholder.

Changed

  • Locales can have up to three subtags and lose one at a time on fallback: zh-Hant-TW → zh-Hant → zh → locale-less. Before, any locale with more than one subtag skipped straight to the locale-less file.
  • render/4 and missing_variables/4 now share one resolution function, so the check always sees the same content the render sends.

0.1.1 - 2026-09-06

Documentation

  • Made the on-disk layout unmissable. A template's name is a directory, not a filename — the one thing the 0.1.0 docs never actually stated, and the first thing anyone writing an override needs to know. Every doc now leads with it and carries a worked tree: PhoenixKit.Templates for the conceptual model and the full resolution order, PhoenixKit.Templates.Overrides for the lookup rules, and the README for practical use.
  • Recorded why there is no slug and no display_name: name is validated as [a-z0-9][a-z0-9_\-]* precisely because it is a path segment, so it is slug-shaped by necessity and a second identifier would address nothing.
  • Folded the duplicated part/extension listing into one table, keyed by which channel reads each part.

No code changes; 0.1.0 and 0.1.1 are functionally identical.

0.1.0 - 2026-09-06

Initial release.

  • PhoenixKit.Templates.render/4 resolves a named template per part — host override for the recipient's locale, then base language, then locale-less, then the caller's default — and substitutes {{variables}}. Parts resolve independently, so a host overriding only html keeps the package's translated subject and text.
  • PhoenixKit.Templates.missing_variables/4 reports unbound placeholders per part, for a test or a preview screen. render/4 itself never raises over one: a message with a flawed line still beats a message that never arrives.
  • PhoenixKit.Templates.Overrides reads host override files at runtime and caches them in :persistent_term, the absence of a file included. name and locale are validated before being joined onto a root.
  • No runtime dependencies, deliberately: phoenix_kit depends on this package, so anything pulled in here lands upstream of the entire tree.