{"id":28804495,"url":"https://github.com/sovetnik/glossary","last_synced_at":"2026-03-15T13:43:43.623Z","repository":{"id":296703820,"uuid":"994227466","full_name":"sovetnik/glossary","owner":"sovetnik","description":"Minimalistic semantic translation system for Elixir apps","archived":false,"fork":false,"pushed_at":"2025-06-13T06:47:33.000Z","size":16,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-18T00:45:15.012Z","etag":null,"topics":["ecto","elixir","i18n","localization","phoenix","yaml"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/glossary","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sovetnik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-01T13:51:52.000Z","updated_at":"2025-06-13T06:54:52.000Z","dependencies_parsed_at":"2025-06-01T22:12:39.361Z","dependency_job_id":"c4003c35-c53b-4a69-a5e5-e925793226b6","html_url":"https://github.com/sovetnik/glossary","commit_stats":null,"previous_names":["sovetnik/glossary"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sovetnik/glossary","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sovetnik%2Fglossary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sovetnik%2Fglossary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sovetnik%2Fglossary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sovetnik%2Fglossary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sovetnik","download_url":"https://codeload.github.com/sovetnik/glossary/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sovetnik%2Fglossary/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260523584,"owners_count":23021968,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ecto","elixir","i18n","localization","phoenix","yaml"],"created_at":"2025-06-18T09:03:56.585Z","updated_at":"2026-03-15T13:43:43.618Z","avatar_url":"https://github.com/sovetnik.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Glossary\n\n**Minimalistic semantic translation system for Elixir apps.**\n\nGlossary is a lightweight and expressive alternative to Gettext for modern Elixir applications — especially Phoenix LiveView.  \nIt embraces semantic lexemes, YAML lexicons, and compile-time localization with a simple and explicit API.\n\nEach YAML file acts as a lexicon — a mapping of semantic keys (lexemes) to localized values (expressions) for a given language.  \nAll lexicons are compiled into the module at build time, enabling fast and predictable lookups at runtime.\n\n---\n\n- [🧱 Concept](#-concept)\n- [✨ Features](#-features)\n- [📄 Lexicons (YAML)](#-lexicons-yaml)\n- [🚀 Quick Start](#-quick-start)\n- [💡 API](#-api)\n- [📚 Best Practices](#-best-practices)\n- [🏛️ Philosophy](#-philosophy)\n- [🔍 Comparison with Gettext](#-comparison-with-gettext)\n- [🧩 Using with Ecto](#-using-with-ecto)\n- [🧠 Acknowledgments](#-acknowledgments)\n- [📬 Feedback](#-feedback)\n\n---\n\n## 🧱 Concept\n\nA **lexeme** is a minimal semantic unit — a key like `\"game.won\"`.  \nAn **expression** is its localized realization — a string like `\"You won!\"`.  \nA **lexicon** is a YAML file that maps lexemes to expressions for a specific language.\n\nTogether, lexicons form a **glossary** — a complete set of localized meanings.\n\n---\n\n## ✨ Features\n\n- 🧠 **Semantic keys** — Use lexemes like `\"game.score\"`, not literal strings.\n- 🔄 **Runtime interpolation** — Simple bindings with `{{key}}` syntax.\n- ⚡ **Live reloadable** — Load translations dynamically, perfect for LiveView.\n- 📄 **YAML-first** — Intuitive, version-friendly format.\n- 🧪 **No hidden magic** — Only explicit macros for setup, no DSLs, no runtime surprises.\n\n---\n\n## 📄 Lexicons (YAML)\n\nEach YAML file represents a **lexicon** — a set of localized expressions.  \nLexicons are merged into a single lookup table keyed by `\"language.lexeme\"`.\n\n```yaml\n# game.en.yml\ngame:\n  won: \"You won!\"\n  lost: \"Game over.\"\n  score: \"Your score: {{score}}\"\n\n# game.ru.yml\ngame:\n  won: \"Вы победили!\"\n  lost: \"Игра окончена.\"\n  score: \"Ваш счёт: {{score}}\"\n\n# user.en.yml\nuser:\n  greeting: \"Hello, {{name}}!\"\n\n# user.ru.yml\nuser:\n  greeting: \"Привет, {{name}}!\"\n```\n\n---\n\n## 🚀 Quick Start\n\n1. Add to your project\n\n# mix.exs\n```elixir\n    def deps do\n      [\n        {:glossary, \"~\u003e 0.1\"}\n      ]\n    end\n```\n\n2. Define a module with glossary and specify lexicon files\n\n```elixir\ndefmodule MyAppWeb.Live.Game.Show do\n  use Glossary, [\"game\", \"../users/user\", \"../common\"]\nend\n```\n\nThis will compile:\n\t•\tgame.en.yml, game.ru.yml\n\t•\tuser.en.yml, user.ru.yml\n\t•\tcommon.en.yml, common.ru.yml\n\n3. Use in LiveView templates\n\n```elixir\n\u003c%= MyAppWeb.Live.Game.Show.t(\"game.score\", @locale, score: 42) %\u003e\n```\n\n4. Missing translation?\n\nYou’ll see the full key on the page (e.g., en.game.score) and a warning in logs:\n\n```shell\n[warning] [Glossary] Missing key: en.game.score\n```\n\n5. Add translation and reload\n\n```yaml\n# game.en.yml\ngame:\n  score: \"Your score: {{score}}\"\n```\n\nNo recompilation needed.\n\n---\n\n## 💡 API\n\nt(lexeme, locale)\nt(lexeme, locale, bindings)\n- Falls back to lexeme if no translation is found.\n- Interpolates placeholders like {{score}} with values from bindings.\n\n---\n\n## 🧩 Using with Ecto\n\nGlossary includes seamless support for Ecto.Changeset errors.\n\n### Setup\n\n```elixir\ndefmodule MyAppWeb.CoreComponents do\n  use Glossary.Ecto, [\"validation\"]\n\n# Add a locale attribute to your input component:\n\n  attr :locale, :string, default: \"en\"\n  def input(%{field: %HTML.FormField{} = field} = assigns) do\n    ...\n    |\u003e assign(:errors, Enum.map(field.errors, \u0026hint(\u00261, assigns.locale)))\n    ...\n  end\n  ...\nend\n```\n\n### In your custom validation:\n\n```elixir\nadd_error(\n  changeset,\n  :field,\n  \"you're doing it wrong\",\n  foo: \"bar\",\n  validation: :foobar\n)\n```\n\nIf there’s no translation yet, the fallback message will be shown (\"you're doing it wrong\"), and a warning will be logged.\n\n\n```yaml\nvalidation:\n  foobar: \"you're doing {{foo}} wrong\"\n```\n\nExample usage in form (important! add locale):\n\n```elixir\n\u003c.input\n  field={@form[:body]}\n  action={@form.source.action}\n  locale={@locale}\n  type=\"text\"\n/\u003e\n```\n\nExample YAML (see: [validation.en.yml](https://github.com/sovetnik/glossary/blob/main/test/support/locales/validation.en.yml))\n\n---\n\n## 📚 Best Practices\n- ✅ Use semantic keys: \"user.greeting\" \u003e \"welcome_text_1\"\n- 📁 Group by domain: user, game, import, etc.\n- 🧩 Prefer flat 2-level keys: domain.key\n- 🔑 Avoid file-based logic — only lexemes and language matter\n- 🪄 Use {{key}} placeholders for dynamic values\n\n---\n\n## 🏛️ Philosophy\n\nGlossary was built for **dynamic apps** — like those using Phoenix LiveView — where UI, state, and translations often evolve together.\n\n### 🔍 Comparison with Gettext\nGlossary is built for interactive, reactive, hot-reloaded systems.\nGettext was designed for monolithic, statically compiled apps in the 1990s. \nPhoenix LiveView is dynamic, reactive, and often developer-translated. \nGlossary brings translation into the runtime flow of development.\n\n| Feature              | Glossary            | Gettext             |\n|----------------------|---------------------|---------------------|\n| ✅ Semantic keys      | Yes (`\"home.title\"`) | No (uses msgid)     |\n| ✏️ YAML format        | Yes                  | No (.po files)      |\n| ♻️ Live reload        | Easy                 | Needs recompilation |\n| 📦 Runtime API        | Simple `t/2`, `t/3`  | Macro-based         |\n| 🧪 Dev experience     | Transparent          | Magic/macros        |\n\n---\n\n### Why move beyond gettext?\n\n- You want **declarative keys** and **semantic structure**\n- You want to **edit translations live**\n- You don’t want to manage `.po` files or run compilers\n- You want your **UI and language logic to stay in sync**\n\n---\n\n## 🧠 Acknowledgments\n\nInspired by real-world needs of building modern Phoenix LiveView apps with:\n- ✨ Declarative UIs\n- 🔁 Dynamic state\n- 🛠️ Developer-driven i18n\n\n---\n\n## 📬 Feedback\n\nGlossary is small, hackable, and stable — and we’re open to ideas.\nRaise an issue, suggest a feature, or just use it and tell us how it goes.\n\nLet your translations be as clean as your code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsovetnik%2Fglossary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsovetnik%2Fglossary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsovetnik%2Fglossary/lists"}