A minimalistic translation system for Elixir applications
Glot provides a simple translation system for Elixir applications using JSONL glossaries. It supports interpolation, live reloading in development, and semantic lexeme-based translations.
- High Performance: Direct ETS table access for fast lookups
- Multi-locale Support: Easy locale switching with fallback support
- Live Reloading: Automatic translation updates during development
- Interpolation: Dynamic content with
{{variable}}
placeholders - Semantic Keys: Meaning-based lexemes instead of UI position keys
- Module Isolation: Each module gets its own isolated translation table
- Minimal Dependencies: Only requires
jason
andfile_system
Add glot
to your list of dependencies in mix.exs
:
def deps do
[
{:glot, "~> 0.1.0"}
]
end
Create JSONL files for each locale and domain:
priv/translations/game.en.jsonl
:
{"key": "game.won", "value": "You won!"}
{"key": "game.lost", "value": "Game over."}
{"key": "game.score", "value": "Your score: {{score}}"}
{"key": "game.greeting", "value": "Hello, {{name}}!"}
priv/translations/game.ru.jsonl
:
{"key": "game.won", "value": "Вы победили!"}
{"key": "game.lost", "value": "Игра окончена."}
{"key": "game.score", "value": "Ваш счёт: {{score}}"}
{"key": "game.greeting", "value": "Привет, {{name}}!"}
defmodule MyApp.Game do
use Glot,
base: "priv/translations",
sources: ["game"],
default_locale: "en",
watch: true # Enable live reloading in development
def welcome_message(name) do
t("game.greeting", name: name)
end
def score_message(score) do
t("game.score", score: score)
end
def win_message do
t("game.won")
end
end
# Basic translation
MyApp.Game.win_message()
# => "You won!"
# With interpolation
MyApp.Game.welcome_message("Alice")
# => "Hello, Alice!"
# Different locale
MyApp.Game.t("game.won", "ru")
# => "Вы победили!"
# With interpolation in different locale
MyApp.Game.t("game.score", "ru", score: 100)
# => "Ваш счёт: 100"
Option | Type | Default | Description |
---|---|---|---|
base |
String |
Required | Base directory for translation files |
sources |
[String] |
Required | List of translation source names |
default_locale |
String |
"en" |
Default locale for translations |
watch |
Boolean |
false |
Enable live reloading in development |
Translates a lexeme key to the specified locale with optional interpolation.
# Basic translation
t("game.won")
# => "You won!"
# With locale
t("game.won", "ru")
# => "Вы победили!"
# With interpolation
t("game.score", "en", score: 42)
# => "Your score: 42"
# With locale and interpolation
t("game.greeting", "ru", name: "Алиса")
# => "Привет, Алиса!"
Reloads translations from files. Useful after manual file changes.
MyApp.Game.reload()
# => :ok
Checks if there are uncommitted changes in the translation table.
MyApp.Game.has_changes?()
# => false
Searches for translation keys containing a substring in a specific locale.
MyApp.Game.grep_keys("en", "game")
# => [{"en.game.won", "You won!"}, {"en.game.lost", "Game over."}]
Lists loaded files for this module.
MyApp.Game.loaded_files()
# => [""priv/translations/game.en.jsonl", "priv/translations/game.ru.jsonl"]
Glot
: Main module providing theuse Glot
macroGlot.Translator
: GenServer managing translation tables and lookupsGlot.Lexicon
: Compiles JSONL files into translation mapsGlot.Watcher
: File system watcher for live reloadingGlot.Jsonl
: JSONL file parser
- ETS Tables: Fast in-memory lookups using Erlang's ETS
- Direct Access: Bypasses GenServer calls for maximum performance
- Lazy Loading: GenServer starts automatically on first use
- Module Isolation: Each module gets its own ETS table
Use meaningful, semantic keys instead of UI position keys:
# Good - semantic meaning
{"key": "game.won", "value": "You won!"}
{"key": "user.greeting", "value": "Hello, {{name}}!"}
# Bad - UI position
{"key": "label.bottom_right", "value": "Submit"}
{"key": "button.header", "value": "Save"}
Group translations by domain in separate files:
priv/translations/
├── game.en.jsonl # Game-related translations
├── game.ru.jsonl
├── user.en.jsonl # User-related translations
├── user.ru.jsonl
├── validation.en.jsonl # Validation messages
└── validation.ru.jsonl
Use {{variable}}
placeholders for dynamic content:
{"key": "user.welcome", "value": "Welcome back, {{name}}!"}
{"key": "game.score", "value": "Score: {{score}} points"}
{"key": "validation.required", "value": "{{field}} is required"}
Enable live reloading in development by setting watch: true
:
use Glot,
base: "priv/translations",
sources: ["game"],
default_locale: "en",
watch: true # Automatically reloads when files change
defmodule MyApp.GameTest do
use ExUnit.Case
defmodule TestGame do
use Glot,
base: "test/fixtures",
sources: ["game"],
default_locale: "en"
end
test "translates basic messages" do
assert TestGame.t("game.won") == "You won!"
end
test "handles interpolation" do
assert TestGame.t("game.score", score: 100) == "Score: 100"
end
end
If files are not properly picked up, see what is actually being loaded:
Glot.Lexicon.compile("priv/seeds/cms", ["home", "about", "pricing"])
This project is licensed under the MIT License - see the LICENSE file for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Glot is designed for efficient translation lookups:
- Memory efficient: Uses ETS tables for fast access
- Direct table access: Bypasses GenServer calls for hot paths
- Lazy initialization: GenServer starts only when needed
- Gettext - Elixir's official internationalization library
- ExI18n - Alternative i18n solution
- Cldr - Unicode CLDR data for Elixir
- glossary - Minimalistic semantic translation system for Elixir apps
Made with ❤️ for the Elixir community