Minimalistic semantic translation system for Elixir apps.
Glossary is a lightweight and expressive alternative to Gettext for modern Elixir applications — especially Phoenix LiveView.
It embraces semantic lexemes, YAML lexicons, and compile-time localization with a simple and explicit API.
Each YAML file acts as a lexicon — a mapping of semantic keys (lexemes) to localized values (expressions) for a given language.
All lexicons are compiled into the module at build time, enabling fast and predictable lookups at runtime.
- 🧱 Concept
- ✨ Features
- 📄 Lexicons (YAML)
- 🚀 Quick Start
- 💡 API
- 📚 Best Practices
- 🏛️ Philosophy
- 🔍 Comparison with Gettext
- 🧩 Using with Ecto
- 🧠 Acknowledgments
- 📬 Feedback
A lexeme is a minimal semantic unit — a key like "game.won".
An expression is its localized realization — a string like "You won!".
A lexicon is a YAML file that maps lexemes to expressions for a specific language.
Together, lexicons form a glossary — a complete set of localized meanings.
- 🧠 Semantic keys — Use lexemes like
"game.score", not literal strings. - 🔄 Runtime interpolation — Simple bindings with
{{key}}syntax. - ⚡ Live reloadable — Load translations dynamically, perfect for LiveView.
- 📄 YAML-first — Intuitive, version-friendly format.
- 🧪 No hidden magic — Only explicit macros for setup, no DSLs, no runtime surprises.
Each YAML file represents a lexicon — a set of localized expressions.
Lexicons are merged into a single lookup table keyed by "language.lexeme".
# game.en.yml
game:
won: "You won!"
lost: "Game over."
score: "Your score: {{score}}"
# game.ru.yml
game:
won: "Вы победили!"
lost: "Игра окончена."
score: "Ваш счёт: {{score}}"
# user.en.yml
user:
greeting: "Hello, {{name}}!"
# user.ru.yml
user:
greeting: "Привет, {{name}}!"- Add to your project
def deps do
[
{:glossary, "~> 0.1"}
]
end- Define a module with glossary and specify lexicon files
defmodule MyAppWeb.Live.Game.Show do
use Glossary, ["game", "../users/user", "../common"]
endThis will compile: • game.en.yml, game.ru.yml • user.en.yml, user.ru.yml • common.en.yml, common.ru.yml
- Use in LiveView templates
<%= MyAppWeb.Live.Game.Show.t("game.score", @locale, score: 42) %>- Missing translation?
You’ll see the full key on the page (e.g., en.game.score) and a warning in logs:
[warning] [Glossary] Missing key: en.game.score- Add translation and reload
# game.en.yml
game:
score: "Your score: {{score}}"No recompilation needed.
t(lexeme, locale) t(lexeme, locale, bindings)
- Falls back to lexeme if no translation is found.
- Interpolates placeholders like {{score}} with values from bindings.
Glossary includes seamless support for Ecto.Changeset errors.
defmodule MyAppWeb.CoreComponents do
use Glossary.Ecto, ["validation"]
# Add a locale attribute to your input component:
attr :locale, :string, default: "en"
def input(%{field: %HTML.FormField{} = field} = assigns) do
...
|> assign(:errors, Enum.map(field.errors, &hint(&1, assigns.locale)))
...
end
...
endadd_error(
changeset,
:field,
"you're doing it wrong",
foo: "bar",
validation: :foobar
)If there’s no translation yet, the fallback message will be shown ("you're doing it wrong"), and a warning will be logged.
validation:
foobar: "you're doing {{foo}} wrong"Example usage in form (important! add locale):
<.input
field={@form[:body]}
action={@form.source.action}
locale={@locale}
type="text"
/>Example YAML (see: validation.en.yml)
- ✅ Use semantic keys: "user.greeting" > "welcome_text_1"
- 📁 Group by domain: user, game, import, etc.
- 🧩 Prefer flat 2-level keys: domain.key
- 🔑 Avoid file-based logic — only lexemes and language matter
- 🪄 Use {{key}} placeholders for dynamic values
Glossary was built for dynamic apps — like those using Phoenix LiveView — where UI, state, and translations often evolve together.
Glossary is built for interactive, reactive, hot-reloaded systems. Gettext was designed for monolithic, statically compiled apps in the 1990s. Phoenix LiveView is dynamic, reactive, and often developer-translated. Glossary brings translation into the runtime flow of development.
| Feature | Glossary | Gettext |
|---|---|---|
| ✅ Semantic keys | Yes ("home.title") |
No (uses msgid) |
| ✏️ YAML format | Yes | No (.po files) |
| ♻️ Live reload | Easy | Needs recompilation |
| 📦 Runtime API | Simple t/2, t/3 |
Macro-based |
| 🧪 Dev experience | Transparent | Magic/macros |
- You want declarative keys and semantic structure
- You want to edit translations live
- You don’t want to manage
.pofiles or run compilers - You want your UI and language logic to stay in sync
Inspired by real-world needs of building modern Phoenix LiveView apps with:
- ✨ Declarative UIs
- 🔁 Dynamic state
- 🛠️ Developer-driven i18n
Glossary is small, hackable, and stable — and we’re open to ideas. Raise an issue, suggest a feature, or just use it and tell us how it goes.
Let your translations be as clean as your code.