Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
624ff4e
Update gettext version to 0.26.0+
arturz Feb 26, 2025
ff88a3c
Create Kanta gettext adapter
arturz Feb 26, 2025
892289b
Create custom gettext module
arturz Feb 26, 2025
0a3a043
Remove Gettext repo module
arturz Feb 26, 2025
3cb0848
Add elixirc_paths and postgrex
arturz Mar 6, 2025
87c6f44
Update Gettext configuration instruction
arturz Mar 6, 2025
162ddf2
Configure test section in config file
arturz Mar 6, 2025
27ba432
Handle interpolations in messages from DB or cache
arturz Mar 6, 2025
c30b430
Add create_locale function
arturz Mar 6, 2025
1aa2c9f
Set up tests helpers
arturz Mar 6, 2025
c7daeb9
Remove TODO
arturz Mar 6, 2025
0e3b729
Pass bindings into adapter functions
arturz Mar 6, 2025
c5c4f28
Create fixtures for tests
arturz Mar 6, 2025
92ba19e
Create basic test cases for Kanta.Gettext module
arturz Mar 6, 2025
0f57bf0
Add multi_messages fixtures
arturz Mar 7, 2025
b0771f0
Create extractor test
arturz Mar 7, 2025
4964824
Create macros test
arturz Mar 7, 2025
b554a2a
Create modified gettext test
arturz Mar 7, 2025
5cb544b
Remove unused alias
arturz Mar 7, 2025
4ac0d48
Change typespec to satisfy both 1.14 and 1.15+ formatters
arturz Mar 7, 2025
a26baa5
Update GitHub workflow
arturz Mar 11, 2025
2cbb064
Delegate all public functions in Gettext module
arturz Mar 14, 2025
47d024c
Update warnings to use Kanta.Gettext
arturz Mar 14, 2025
7fd4746
Merge pull request #113 from curiosum-dev/develop
jk-lamb Mar 24, 2025
6688ac3
Gettext 0.26.0 compatibility
jk-lamb Mar 26, 2025
d267726
Fix: non async tests for DB-related operation.
jk-lamb Mar 28, 2025
6e86ced
Minor fixes
jk-lamb Mar 28, 2025
58e9674
Update README.md
jk-lamb Sep 27, 2025
f823aaa
Merge branch 'develop' into 108-base-on-gettext-026
jk-lamb Sep 27, 2025
040f1fb
Update mix.exs: Relax gettext dependency constraint (0.26.*)
jk-lamb Sep 30, 2025
ef09ad0
Update README.md
jk-lamb Sep 30, 2025
0eb26a9
Update CHANGELOG.md
jk-lamb Sep 30, 2025
9fdeefd
Add missing @moduledoc annotations
jk-lamb Sep 30, 2025
3af9c65
Update README.md
jk-lamb Sep 30, 2025
465a285
Add migration step to GitHub Actions database setup
jk-lamb Sep 30, 2025
c0afd52
Address PR review feedback
jk-lamb Oct 7, 2025
1bbb126
Address PR review feedback
jk-lamb Oct 7, 2025
bb1666b
Move recompilation flags to _build directory
jk-lamb Oct 7, 2025
27e71af
Merge branch '108-base-on-gettext-026' of https://github.com/curiosum…
jk-lamb Oct 7, 2025
18fc101
Rename cached_db test file to match module name
jk-lamb Oct 8, 2025
c66121c
Fix line break in cached DB test
jk-lamb Oct 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions .github/workflows/development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ permissions:
jobs:
build:
name: OS ${{matrix.os}} / Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}}
services:
db:
image: postgres:16
ports: ["5432:5432"]
env:
POSTGRES_USER: ${{ vars.POSTGRES_USERNAME }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
matrix:
elixir: ['1.14', '1.15', '1.16', '1.17']
Expand Down Expand Up @@ -48,16 +60,16 @@ jobs:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Set up Postgres
run: |
sudo apt-get update
sudo apt-get install -y postgresql
sudo service postgresql start
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'postgres';"
- name: Install dependencies
run: mix deps.get
- name: Create database
run: mix do ecto.create, ecto.migrate
- name: Reset database and run migrations
env:
POSTGRES_USERNAME: ${{ vars.POSTGRES_USERNAME }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_HOSTNAME: ${{ vars.POSTGRES_HOSTNAME }}
SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }}
MIX_ENV: test
run: mix ecto.drop && mix ecto.create && mix ecto.migrate
- name: Compile code
run: mix compile --warnings-as-errors
- name: Check Formatting
Expand All @@ -67,4 +79,10 @@ jobs:
- name: Credo
run: mix credo
- name: Run tests
run: MIX_ENV=test mix test
env:
POSTGRES_USERNAME: ${{ vars.POSTGRES_USERNAME }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_HOSTNAME: ${{ vars.POSTGRES_HOSTNAME }}
SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }}
MIX_ENV: test
run: mix test
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## Unreleased
## [v0.5.0]

### Breaking Changes
- **Gettext 0.26.0 Migration**: This version requires updating your Gettext module definition to use the new backend adapter system. You must define `use Kanta.Gettext.Backend` in your Gettext module and configure the adapter.

### Added
- Adds Gettext 0.26.0 compatibility with a custom backend adapter system

## [v0.4.2]
### Fixed
Expand Down
73 changes: 65 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ If you're working on an Elixir/Phoenix project and need to manage translations,
</ul>
</li>
<li><a href="#roadmap">Roadmap</a></li>
<li>
<a href="#development">Development</a>
<ul>
<li><a href="#running-tests">Running Tests</a></li>
</ul>
</li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#license">License</a></li>
<li><a href="#contact">Contact</a></li>
Expand Down Expand Up @@ -116,13 +122,10 @@ by adding `kanta` to your list of dependencies in `mix.exs`:
def deps do
[
{:kanta, "~> 0.4.2"},
{:gettext, git: "[email protected]:ravensiris/gettext.git", branch: "runtime-gettext"}
]
end
```

The dependency on this specific `gettext` version is because this library depends on an in-progress feature, to be included in a future release of `gettext` (see discussion in elixir-gettext/gettext#280 and pull request elixir-gettext/gettext#305). As of March 2023, this has been approved by an Elixir core team member, so we are eagerly awaiting for it being merged upstream.

## Configuration

Add to `config/config.exs` file:
Expand All @@ -148,6 +151,12 @@ mix ecto.gen.migration add_kanta_translations_table

Open the generated migration file and set up `up` and `down` functions.

**Current Migration Versions:**
- PostgreSQL: **v4** (adds default context support for Gettext 0.26 backend)
- SQLite: **v3** (adds default context support for Gettext 0.26 backend)

If you're upgrading from an earlier version of Kanta, update your migration version to the latest.

### PostgreSQL

```elixir
Expand All @@ -160,25 +169,52 @@ defmodule MyApp.Repo.Migrations.AddKantaTranslationsTable do

# We specify `version: 1` because we want to rollback all the way down including the first migration.
def down do
Kanta.Migration.down(version: 4, prefix: prefix()) # Prefix is needed if you are using multitenancy with i.e. triplex
Kanta.Migration.down(version: 1, prefix: prefix()) # Prefix is needed if you are using multitenancy with i.e. triplex
end
end
```

after that run
### SQLite

```elixir
defmodule MyApp.Repo.Migrations.AddKantaTranslationsTable do
use Ecto.Migration

def up do
Kanta.Migration.up(version: 3)
end

# We specify `version: 1` because we want to rollback all the way down including the first migration.
def down do
Kanta.Migration.down(version: 1)
end
end
```

After that run:

```bash
mix ecto.migrate
```

## Gettext module

We now need to pass information to our project's `Gettext` module that we want Kanta to manage translations. To do this add `Kanta.Gettext.Repo` as a default translation repository inside your `Gettext` module.
Configuring Gettext requires just a single change.

Wherever you have:

```elixir
use Gettext, ..., repo: Kanta.Gettext.Repo
use Gettext, backend: YourApp.Gettext
```

replace it with:

```elixir
use Kanta.Gettext, backend: YourApp.Gettext
```

If you're using a Gettext version lower than 0.26, refer to the [official documentation](https://github.com/elixir-gettext/gettext) for migration instructions.

## Kanta Supervisor

In the `application.ex` file of our project, we add Kanta and its configuration to the list of processes.
Expand Down Expand Up @@ -222,7 +258,9 @@ Kanta is based on the Phoenix Framework's default localization tool, GNU gettext

<img style="margin-top: 1rem; margin-bottom: 1rem;" src="./singular.png" alt="singular">

Messages and translations from .po files are stored in tables created by the Kanta.Migration module. This allows easy viewing and modification of messages from the Kanta UI or directly from database tools. The caching mechanism prevents constant requests to the database when downloading translations, so you don't have to worry about a delay in application performance.
Messages and translations from .po files are stored in tables created by the Kanta.Migration module. This allows easy viewing and modification of messages from the Kanta UI or directly from database tools.

With Gettext 0.26+, Kanta uses a custom backend adapter system (`Kanta.Backend.Adapter.CachedDB`) that fetches translations from the database/cache at runtime instead of compiled PO files. The caching mechanism prevents constant requests to the database when downloading translations, so you don't have to worry about a delay in application performance.

## Translation progress

Expand Down Expand Up @@ -323,6 +361,25 @@ See the [open issues](https://github.com/curiosum-dev/kanta/issues) for a full l

<p align="right">(<a href="#readme-top">back to top</a>)</p>

# Development

## Running Tests

If you're contributing to Kanta development, you'll need to run the test suite. The tests require a PostgreSQL database.

### Prerequisites for Development
- PostgreSQL 15+ (for running tests)
- All prerequisites listed in [Getting Started](#prerequisites)

### Test Setup

First-time setup (or if tests are failing due to database issues):

```bash
# Setup test database and run migrations
MIX_ENV=test mix ecto.drop && MIX_ENV=test mix ecto.create && MIX_ENV=test mix ecto.migrate
```

<!-- CONTRIBUTING -->

## Contributing
Expand Down
14 changes: 14 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,17 @@ if config_env() == :dev do
tag_template: "v{version}",
tag_message_template: "Release v{version}"
end

if config_env() == :test do
config :kanta,
ecto_repos: [Kanta.Test.Repo]

config :kanta, Kanta.Test.Repo,
username: System.get_env("POSTGRES_USERNAME", "postgres"),
password: System.get_env("POSTGRES_PASSWORD", "postgres"),
hostname: System.get_env("POSTGRES_HOSTNAME", "localhost"),
database: "kanta_test",
port: 5432,
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 10
end
116 changes: 116 additions & 0 deletions lib/kanta/backend.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
defmodule Kanta.Backend do
@moduledoc """
Kanta.Backend is a module that provides an enhanced Gettext backend with database support.

It extends the standard Gettext functionality by:
1. First checking for translations in the database
2. Falling back to PO file translations if not found in the database

## Usage

```elixir
defmodule MyApp.Gettext do
use Kanta.Backend, otp_app: :my_app
end
```

## Options

* `:otp_app` - The OTP application that contains the backend
* `:priv` - The directory where the translations are stored (defaults to "priv/YOUR_MODULE")
* `:kanta_adapter` - The adapter module to use for database lookups (defaults to `Kanta.Backend.Adapter.CachedDB`)

it also accepts all the Gettext.Backend options. See the official Gettext documentation for more details.


"""
alias Kanta.Utils.ModuleFolder
require Logger

defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
require Logger
@flag_file Path.join([Mix.Project.build_path(), "kanta_recompile", ".gettext_recompiled"])
@adapter Keyword.get(opts, :kanta_adapter, Kanta.Backend.Adapter.CachedDB)
opts = Keyword.drop(opts, [:kanta_adapter])
# Generate fallback Gettext backend form PO files
use Kanta.Backend.GettextFallback, opts

# When `mix gettext extract` create POT/PO files based on this backend usage (ex. getext(...) call) across the application codebase.
if Gettext.Extractor.extracting?() do
use Gettext.Backend, opts

Kanta.Utils.GettextRecompiler.setup_recompile_flag(@flag_file)
else
opts = Keyword.merge(opts, priv: "priv/#{ModuleFolder.safe_folder_name(__MODULE__)}")
use Gettext.Backend, opts
end

def __mix_recompile__?() do
Kanta.Utils.GettextRecompiler.needs_recompile?(@flag_file)
end

def __gettext__(:known_locales) do
backend = fallback_backend()
Gettext.known_locales(backend)
end

def handle_missing_translation(locale, domain, msgctxt, msgid, bindings) do
case Kanta.Backend.Adapter.CachedDB.lgettext(
locale,
domain,
msgctxt,
msgid,
bindings
) do
{:ok, translation} ->
{:ok, translation}

{:error, :not_found} ->
backend = fallback_backend()
backend.lgettext(locale, domain, msgctxt, msgid, bindings)
end
end

def handle_missing_plural_translation(
locale,
domain,
msgctxt,
msgid,
msgid_plural,
n,
bindings
) do
case Kanta.Backend.Adapter.CachedDB.lngettext(
locale,
domain,
msgctxt,
msgid,
msgid_plural,
n,
bindings
) do
{:ok, translation} ->
{:ok, translation}

{:error, :not_found} ->
backend = fallback_backend()

backend.lngettext(
locale,
domain,
msgctxt,
msgid,
msgid_plural,
n,
bindings
)
end
end

defp fallback_backend() do
Module.concat(__MODULE__, GettextFallbackBackend)
end
end
end
end
Loading
Loading