Skip to content

Commit

Permalink
Add :persistent_term support
Browse files Browse the repository at this point in the history
  • Loading branch information
dolfinus committed Mar 21, 2021
1 parent 74719e6 commit 071cba6
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 41 deletions.
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,10 @@ The key to use for pluralization is configurable, and should likely be an atom:
config :linguist, pluralization_key: :count
```
will cause the system to pluralize based on the `count` parameter passed to the `t` function.

Also you can use `:persistent_term` 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
80 changes: 57 additions & 23 deletions lib/linguist/memorized_vocabulary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,49 @@ 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()

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 +85,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 +111,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 +136,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 +177,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

0 comments on commit 071cba6

Please sign in to comment.