-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a685846
commit 935258b
Showing
15 changed files
with
1,845 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.