Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add :persistent_term support #39

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 16 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ on:
jobs:
test:
name: Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}}
runs-on: ubuntu-latest
runs-on: ubuntu-18.04

strategy:
matrix:
include:
- elixir: '1.11'
otp: '23'
- elixir: '1.10'
otp: '22'
elixir:
- "1.9"
- "1.10"
- "1.11"
otp:
- "21.0"
- "22.0.2"
- "23.0"

steps:
- name: Checkout
Expand All @@ -37,8 +40,6 @@ jobs:
restore-keys: |
${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}
${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}
${{ runner.os }}-mix-${{ matrix.otp }}
${{ runner.os }}-mix

- name: Restore _build cache
uses: actions/cache@v2
Expand All @@ -48,8 +49,6 @@ jobs:
restore-keys: |
${{ runner.os }}-build-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}
${{ runner.os }}-build-${{ matrix.otp }}-${{ matrix.elixir }}
${{ runner.os }}-build-${{ matrix.otp }}
${{ runner.os }}-build

- name: Install Dependencies
run: |
Expand All @@ -60,4 +59,10 @@ jobs:
- name: Run unit tests
run: |
mix clean
mix test
mix test

- name: Run unit tests with persistent_term backend
run: mix test
if: matrix.otp != '21.0'
env:
SCHEMA_PROVIDER: persistent_term
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,22 @@ iex> I18n.t!("en", "users.title")

## Configuration

### Pluralization key

The key to use for pluralization is configurable, and should likely be an atom:

```elixir
config :linguist, pluralization_key: :count
```

will cause the system to pluralize based on the `count` parameter passed to the `t` function.

### `:persistent_term` support

Also you can use [`:persistent_term`](https://erlang.org/doc/man/persistent_term.html) backend instead of `:ets` in `Linguist.MemoizedVocabulary` by setting up:

```elixir
config :linguist, vocabulary_backend: :persistent_term
```

**This is only available on OTP >= 21.2**
4 changes: 2 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use Mix.Config

config :linguist, pluralization_key: :count

config :ex_cldr, json_library: Jason

if Mix.env() == :test do
config :linguist, Linguist.Cldr, locales: ["fr", "en", "es"]

config :linguist, vocabulary_backend: (System.get_env("SCHEMA_PROVIDER") || "ets") |> String.to_existing_atom()
end
8 changes: 3 additions & 5 deletions lib/linguist/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@ defmodule Linguist.Compiler do
end

def t(locale, path, bindings) do
pluralization_key = Application.fetch_env!(:linguist, :pluralization_key)

if Keyword.has_key?(bindings, pluralization_key) do
if Keyword.has_key?(bindings, @pluralization_key) do
plural_atom =
bindings
|> Keyword.get(pluralization_key)
|> Keyword.get(@pluralization_key)
|> Cardinal.plural_rule(locale)

new_path = "#{path}.#{plural_atom}"
Expand All @@ -76,7 +74,7 @@ defmodule Linguist.Compiler do
do_t(locale, path, bindings)
end
end

unquote(translations)

def do_t(_locale, _path, _bindings), do: {:error, :no_translation}
Expand Down
81 changes: 58 additions & 23 deletions lib/linguist/memorized_vocabulary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,50 @@ defmodule Linguist.MemorizedVocabulary do
defexception [:message]
end

@pluralization_key Application.get_env(:linguist, :pluralization_key, :count)

if Application.get_env(:linguist, :vocabulary_backend, :ets) == :persistent_term do
if not Code.ensure_loaded?(:persistent_term) do
raise("You've set up linguist to use :persistent_term backend, but it is available only if OTP >= 21.2")
end

def create_backend() do
end

def add_to_backend(key, value) do
:persistent_term.put({__MODULE__, key}, value)
end

def remove_from_backend(key) do
:persistent_term.erase({__MODULE__, key})
end

def get_from_backend(key) do
:persistent_term.get({__MODULE__, key}, nil)
end
else
def create_backend() do
if :ets.info(__MODULE__) == :undefined do
:ets.new(__MODULE__, [:named_table, :set, :protected])
end
end

def add_to_backend(key, value) do
:ets.insert(__MODULE__, {key, value})
end

def remove_from_backend(key) do
:ets.delete(__MODULE__, key)
end

def get_from_backend(key) do
case :ets.lookup(__MODULE__, key) |> List.first() do
{_, value} -> value
nil -> nil
end
end
end

@moduledoc """
Defines lookup functions for given translation locales, binding interopolation

Expand Down Expand Up @@ -42,13 +86,12 @@ defmodule Linguist.MemorizedVocabulary do
end

def t(locale, path, bindings) do
pluralization_key = Application.fetch_env!(:linguist, :pluralization_key)
norm_locale = normalize_locale(locale)

if Keyword.has_key?(bindings, pluralization_key) do
if Keyword.has_key?(bindings, @pluralization_key) do
plural_atom =
bindings
|> Keyword.get(pluralization_key)
|> Keyword.get(@pluralization_key)
|> Cardinal.plural_rule(norm_locale)

do_t(norm_locale, "#{path}.#{plural_atom}", bindings)
Expand All @@ -69,11 +112,13 @@ defmodule Linguist.MemorizedVocabulary do

# sobelow_skip ["DOS.StringToAtom"]
defp do_t(locale, translation_key, bindings) do
case :ets.lookup(:translations_registry, "#{locale}.#{translation_key}") do
[] ->
result = get_from_backend("#{locale}.#{translation_key}")

case result do
nil ->
{:error, :no_translation}

[{_, string}] ->
string ->
translation =
Compiler.interpol_rgx()
|> Regex.split(string, on: [:head, :tail])
Expand All @@ -92,28 +137,22 @@ defmodule Linguist.MemorizedVocabulary do
end

def locales do
tuple =
:ets.lookup(:translations_registry, "memorized_vocabulary.locales")
|> List.first()

if tuple do
elem(tuple, 1)
end
get_from_backend("memorized_vocabulary.locales") || []
end

def add_locale(name) do
current_locales = locales() || []
current_locales = locales()
new_locales = [name | current_locales] |> Enum.uniq()

:ets.insert(
:translations_registry,
{"memorized_vocabulary.locales", [name | current_locales]}
)
add_to_backend("memorized_vocabulary.locales", new_locales)
end

def update_translations(locale_name, loaded_source) do
create_backend()

loaded_source
|> Enum.map(fn {key, translation_string} ->
:ets.insert(:translations_registry, {"#{locale_name}.#{key}", translation_string})
add_to_backend("#{locale_name}.#{key}", translation_string)
end)
end

Expand All @@ -139,10 +178,6 @@ defmodule Linguist.MemorizedVocabulary do
will not work as expected if called directly.
"""
def _load_yaml_file(source) do
if :ets.info(:translations_registry) == :undefined do
:ets.new(:translations_registry, [:named_table, :set, :protected])
end

{decode_status, [file_data]} = YamlElixir.read_all_from_file(source)

if decode_status != :ok do
Expand Down
1 change: 1 addition & 0 deletions lib/linguist/vocabulary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ defmodule Linguist.Vocabulary do
defmacro __using__(_options) do
quote do
Module.register_attribute(__MODULE__, :locales, accumulate: true, persist: false)
@pluralization_key Application.get_env(:linguist, :pluralization_key, :count)
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
end
Expand Down