Skip to content

Commit

Permalink
end of day commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cschmatzler committed Jul 15, 2023
1 parent a685846 commit 935258b
Show file tree
Hide file tree
Showing 15 changed files with 1,845 additions and 73 deletions.
32 changes: 32 additions & 0 deletions lib/idiom/languages.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule Idiom.Languages do
def to_resolve_hierarchy(code, opts \\ []) do
fallback = Keyword.get(opts, :fallback)

([code, get_script_part_from_code(code), get_language_part_from_code(code)] ++ List.wrap(fallback))
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end

def get_language_part_from_code(code) do
if String.contains?(code, "-") do
String.replace(code, "_", "-") |> String.split("-") |> List.first()
else
code
end
end

defp get_script_part_from_code(code) do
if String.contains?(code, "-") do
String.replace(code, "_", "-")
|> String.split("-")
|> case do
nil -> nil
parts when is_list(parts) and length(parts) == 2 -> nil
# TODO: Format language code
parts when is_list(parts) -> Enum.take(parts, 2) |> Enum.join("-")
end
else
code
end
end
end
25 changes: 25 additions & 0 deletions lib/idiom/pluralizer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Idiom.Pluralizer do
alias Idiom.Languages
alias Idiom.Pluralizer.Compiler

import Idiom.Pluralizer.Util

@rules [:code.priv_dir(Mix.Project.config()[:app]), "/idiom"]
|> :erlang.iolist_to_binary()
|> Path.join("/plural_rules.json")
|> File.read!()
|> Jason.decode!()
|> Map.get("cardinal")
|> Enum.map(&Compiler.normalize_locale_rules/1)
|> Map.new()

for {lang, conditions} <- @rules do
defp do_get_plural(unquote(lang), n, i, v, w, f, t, e) do
_ = {n, i, v, w, f, t, e}
unquote(Compiler.rules_to_condition_statement(conditions, __MODULE__))
end
end

def get_plural(lang, count) when is_binary(count), do: get_plural(lang, Decimal.new(count))
def get_plural(lang, count) when is_integer(count), do: do_get_plural(lang, abs(count), abs(count), 0, 0, 0, 0, 0)
end
115 changes: 115 additions & 0 deletions lib/idiom/pluralizer/compiler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
defmodule Idiom.Pluralizer.Compiler do
@ast %{
foo:
{:cond, [],
[
[
do: [
{:->, [],
[
[
{:==, [], [{:n, [], nil}, 10]}
],
1
]},
{:->, [],
[
[
{:>, [], [{:n, [], nil}, 1]}
],
2
]},
{:->, [], [[true], 3]}
]
]
]},
bar: {:cond, [], [[do: [{:->, [], [[true], 10]}]]]}
}

def ast, do: @ast

def normalize_locale_rules({locale, rules}) do
sorted_rules =
Enum.map(rules, fn {"pluralRule-count-" <> category, rule} ->
{:ok, definition} = parse(rule)
{String.to_atom(category), definition}
end)
|> Enum.sort(&plural_sorter/2)

{String.to_atom(locale), sorted_rules}
end

defp plural_sorter({:zero, _}, _), do: true
defp plural_sorter({:one, _}, {other, _}) when other in [:two, :few, :many, :other], do: true
defp plural_sorter({:two, _}, {other, _}) when other in [:few, :many, :other], do: true
defp plural_sorter({:few, _}, {other, _}) when other in [:many, :other], do: true
defp plural_sorter({:many, _}, {other, _}) when other in [:other], do: true
defp plural_sorter(_, _), do: false

def rules_to_condition_statement(rules, module) do
branches =
Enum.map(rules, fn {category, definition} ->
{new_ast, _} = set_operand_module(definition[:rule], module)
rule_to_cond_branch(new_ast, category)
end)

{:cond, [], [[do: move_true_branch_to_end(branches)]]}
end

# We can't assume the order of branches and we need the
# `true` branch at the end since it will always match
# and hence potentially shadow other branches
defp move_true_branch_to_end(branches) do
Enum.sort(branches, fn {:->, [], [[ast], _category]}, _other_branch ->
not (ast == true)
end)
end

# Walk the AST and replace the variable context to that of the calling
# module
defp set_operand_module(ast, module) do
Macro.prewalk(ast, [], fn expr, acc ->
new_expr =
case expr do
{var, [], Elixir} ->
{var, [], nil}

# {var, [], module}
{:mod, _context, [operand, value]} ->
{:mod, [context: Elixir, import: Elixir.Cldr.Math], [operand, value]}

{:within, _context, [operand, range]} ->
{:within, [context: Elixir, import: Elixir.Cldr.Math], [operand, range]}

_ ->
expr
end

{new_expr, acc}
end)
end

# Transform the rule AST into a branch of a `cond` statement
defp rule_to_cond_branch(nil, category) do
{:->, [], [[true], category]}
end

defp rule_to_cond_branch(rule_ast, category) do
{:->, [], [[rule_ast], category]}
end

defp parse(tokens) when is_list(tokens) do
:plural_rules_parser.parse(tokens)
end

defp parse(definition) when is_binary(definition) do
{:ok, tokens, _} =
definition
|> String.to_charlist()
|> :plural_rules_lexer.string()

IO.inspect(tokens)

parse(tokens)
end
end
32 changes: 32 additions & 0 deletions lib/idiom/pluralizer/util.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule Idiom.Pluralizer.Util do
def within(number, range) when is_integer(number) do
number in range
end

def within(number, first..last) when is_float(number) do
number == trunc(number) && number >= first && number <= last
end

def mod(number, modulus) when is_float(number) and is_number(modulus) do
number - Float.floor(number / modulus) * modulus
end

def mod(number, modulus) when is_integer(number) and is_integer(modulus) do
modulo =
number
|> Integer.floor_div(modulus)
|> Kernel.*(modulus)

number - modulo
end

def mod(number, modulus) when is_integer(number) and is_number(modulus) do
modulo =
number
|> Kernel./(modulus)
|> Float.floor()
|> Kernel.*(modulus)

number - modulo
end
end
34 changes: 2 additions & 32 deletions lib/idiom/translator.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule Idiom.Translator do
alias Idiom.Cache
alias Idiom.Languages

def translate(key, opts \\ [])
def translate(nil, _opts), do: ""
Expand All @@ -9,20 +10,12 @@ defmodule Idiom.Translator do

lang = Keyword.get(opts, :to) || raise "No language provided"
{namespace, key} = extract_namespace(key, opts)
langs = to_resolve_hierarchy(lang, opts)
langs = Languages.to_resolve_hierarchy(lang, opts)

Enum.find_value(langs, fn lang -> Cache.get_translation(lang, namespace, key, cache_table_name) end)
end

@doc false
def to_resolve_hierarchy(code, opts \\ []) do
fallback_lang = Keyword.get(opts, :fallback_lang)

([code, get_script_part_from_code(code), get_language_part_from_code(code)] ++ List.wrap(fallback_lang))
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end

defp extract_namespace(key, opts) do
default_namespace = Keyword.get(opts, :default_namespace, "translation")
namespace_separator = Keyword.get(opts, :namespace_separator, ":")
Expand All @@ -35,27 +28,4 @@ defmodule Idiom.Translator do
{default_namespace, key}
end
end

defp get_script_part_from_code(code) do
if String.contains?(code, "-") do
String.replace(code, "_", "-")
|> String.split("-")
|> case do
nil -> nil
parts when is_list(parts) and length(parts) == 2 -> nil
# TODO: Format language code
parts when is_list(parts) -> Enum.take(parts, 2) |> Enum.join("-")
end
else
code
end
end

defp get_language_part_from_code(code) do
if String.contains?(code, "-") do
String.replace(code, "_", "-") |> String.split("-") |> List.first()
else
code
end
end
end
8 changes: 7 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Idiom.MixProject do
description: "Modern internationalization library",
version: "0.1.0",
elixir: "~> 1.13",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps(),
package: package()
Expand All @@ -19,9 +20,14 @@ defmodule Idiom.MixProject do
]
end

defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

defp deps do
[
{:ex_doc, "~> 0.30.2"}
{:jason, "~> 1.0"},
{:decimal, "~> 2.1"},
{:ex_doc, "~> 0.30.2", only: [:dev]}
]
end

Expand Down
7 changes: 7 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
%{
"cldr_utils": {:hex, :cldr_utils, "2.24.1", "5ff8c8c55f96666228827bcf85a23d632022def200566346545d01d15e4c30dc", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "1820300531b5b849d0bc468e5a87cd64f8f2c5191916f548cbe69b2efc203780"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"},
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
"ex_cldr": {:hex, :ex_cldr, "2.37.2", "c45041534ec60af367c4c1af02a608576118044fe3c441c782fd424061d6b517", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "c8467b1d5080716ace6621703b6656cb2f9545572a54b341da900791a0cf92ba"},
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.0", "aadd34e91cfac7ef6b03fe8f47f8c6fa8c5daf3f89b5d9fee64ec545ded839cf", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0521316396c66877a2d636219767560bb2397c583341fcb154ecf9f3000e6ff8"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.31.2", "e27a457d594aefd1981094178f95c2efbd0f69c4a0c649c5f4cf5c97e264f310", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "828c0f39df6cc64bb7c586d7d302321322bc65c5d1d5d6115f632c3711d218b7"},
"ex_doc": {:hex, :ex_doc, "0.30.2", "7a3e63ddb387746925bbbbcf6e9cb00e43c757cc60359a2b40059aea573e3e57", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5ba8cb61d069012f16b50e575b0e3e6cf4083935f7444fab0d92c9314ce86bb6"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
Expand Down
Loading

0 comments on commit 935258b

Please sign in to comment.