Initial commit
This commit is contained in:
commit
f3b057a7be
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
localizator-*.tar
|
||||
|
||||
/config/*.secret.exs
|
||||
|
||||
# Ignore vscode config.
|
||||
/.vscode/
|
6
.iex.exs
Normal file
6
.iex.exs
Normal file
@ -0,0 +1,6 @@
|
||||
alias Localizator.Parser
|
||||
alias Localizator.Parser.{Base, JSON}
|
||||
alias Localizator.Translator
|
||||
alias Localizator.Translator.{Yandex, Direction}
|
||||
alias Localizator.Translitor
|
||||
alias Localizator.Translitor.{UK, BE, RU}
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Localizator
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `localizator` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:localizator, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at [https://hexdocs.pm/localizator](https://hexdocs.pm/localizator).
|
||||
|
19
config/config.exs
Normal file
19
config/config.exs
Normal file
@ -0,0 +1,19 @@
|
||||
use Mix.Config
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, :console,
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
level: :info,
|
||||
metadata: [:request_id]
|
||||
|
||||
config :localizator,
|
||||
default_language: "en",
|
||||
translators: [Localizator.Translator.Yandex],
|
||||
translitors: [
|
||||
Localizator.Translitor.BE,
|
||||
Localizator.Translitor.RU,
|
||||
Localizator.Translitor.UK
|
||||
],
|
||||
parsers: [Localizator.Parser.YAML, Localizator.Parser.JSON]
|
||||
|
||||
import_config "#{Mix.env()}.exs"
|
3
config/dev.exs
Normal file
3
config/dev.exs
Normal file
@ -0,0 +1,3 @@
|
||||
import Config
|
||||
|
||||
import_config "dev.secret.exs"
|
1
config/prod.exs
Normal file
1
config/prod.exs
Normal file
@ -0,0 +1 @@
|
||||
import Config
|
1
config/test.exs
Normal file
1
config/test.exs
Normal file
@ -0,0 +1 @@
|
||||
import Config
|
18
lib/commons/commons.ex
Normal file
18
lib/commons/commons.ex
Normal file
@ -0,0 +1,18 @@
|
||||
defmodule Localizator.Commons do
|
||||
def struct_from_map(a_map, as: a_struct) do
|
||||
# Find the keys within the map
|
||||
keys =
|
||||
Map.keys(a_struct)
|
||||
|> Enum.filter(fn x -> x != :__struct__ end)
|
||||
|
||||
# Process map, checking for both string / atom keys
|
||||
processed_map =
|
||||
for key <- keys, into: %{} do
|
||||
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
|
||||
{key, value}
|
||||
end
|
||||
|
||||
a_struct = Map.merge(a_struct, processed_map)
|
||||
a_struct
|
||||
end
|
||||
end
|
18
lib/localizator.ex
Normal file
18
lib/localizator.ex
Normal file
@ -0,0 +1,18 @@
|
||||
defmodule Localizator do
|
||||
@moduledoc """
|
||||
Documentation for `Localizator`.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Hello world.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Localizator.hello()
|
||||
:world
|
||||
|
||||
"""
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
20
lib/parser/base.ex
Normal file
20
lib/parser/base.ex
Normal file
@ -0,0 +1,20 @@
|
||||
defmodule Localizator.Parser.Base do
|
||||
@typedoc """
|
||||
Text contents of the file for parse in Map
|
||||
"""
|
||||
@type contents :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Data in Map format for encoding to text contents of the file
|
||||
"""
|
||||
@type data :: map
|
||||
|
||||
@typedoc """
|
||||
List of the file extensions for parser
|
||||
"""
|
||||
@type extensions :: [String.t()]
|
||||
|
||||
@callback parse(contents) :: {:ok, data} | {:error, any}
|
||||
@callback generate(data) :: {:ok, contents} | {:error, any}
|
||||
@callback extensions() :: {:ok, extensions}
|
||||
end
|
25
lib/parser/json.ex
Normal file
25
lib/parser/json.ex
Normal file
@ -0,0 +1,25 @@
|
||||
defmodule Localizator.Parser.JSON do
|
||||
@extensions ["json"]
|
||||
|
||||
@type contents :: String.t()
|
||||
@type data :: map
|
||||
@type extensions :: [String.t()]
|
||||
|
||||
@behaviour Localizator.Parser.Base
|
||||
|
||||
@impl true
|
||||
@spec parse(contents) :: {:ok, data} | {:error, atom | Jason.DecodeError.t()}
|
||||
def parse(contents) do
|
||||
Jason.decode(contents)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec generate(data) :: {:ok, contents} | {:error, any}
|
||||
def generate(data) do
|
||||
Jason.encode(data)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec extensions() :: {:ok, extensions}
|
||||
def extensions(), do: {:ok, @extensions}
|
||||
end
|
42
lib/parser/yaml.ex
Normal file
42
lib/parser/yaml.ex
Normal file
@ -0,0 +1,42 @@
|
||||
defmodule Localizator.Parser.YAML do
|
||||
@indent " "
|
||||
@extensions ["yml", "yaml"]
|
||||
|
||||
@type contents :: String.t()
|
||||
@type data :: map
|
||||
@type extensions :: [String.t()]
|
||||
|
||||
@behaviour Localizator.Parser.Base
|
||||
|
||||
@impl true
|
||||
@spec parse(contents) :: {:ok, data} | {:error, atom | Jason.DecodeError.t()}
|
||||
def parse(contents) do
|
||||
YamlElixir.read_from_string(contents)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec generate(data) :: {:ok, contents} | {:error, any}
|
||||
def generate(data) do
|
||||
{:ok, to_yaml(data)}
|
||||
end
|
||||
|
||||
defp to_yaml(data, indentation \\ "") do
|
||||
data
|
||||
|> Map.keys()
|
||||
|> Enum.map(fn key ->
|
||||
Map.fetch!(data, key)
|
||||
|> value_to_yaml(key, indentation)
|
||||
end)
|
||||
|> Enum.join("\n")
|
||||
end
|
||||
|
||||
defp value_to_yaml(value, key, indentation) when is_number(value) or is_bitstring(value),
|
||||
do: "#{indentation}#{key}: #{value}"
|
||||
|
||||
defp value_to_yaml(value, key, indentation) when is_map(value),
|
||||
do: "#{indentation}#{key}:\n#{to_yaml(value, "#{indentation}#{@indent}")}"
|
||||
|
||||
@impl true
|
||||
@spec extensions() :: {:ok, extensions}
|
||||
def extensions(), do: {:ok, @extensions}
|
||||
end
|
22
lib/translator/base.ex
Normal file
22
lib/translator/base.ex
Normal file
@ -0,0 +1,22 @@
|
||||
defmodule Localizator.Translator.Base do
|
||||
@typedoc """
|
||||
Plain Text
|
||||
"""
|
||||
@type text :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
@type from :: locale
|
||||
@type to :: locale
|
||||
|
||||
@typedoc """
|
||||
Error Message
|
||||
"""
|
||||
@type message :: String.t()
|
||||
|
||||
@callback detect(text) :: {:ok, locale} | {:error, message}
|
||||
@callback translate(text, to) :: {:ok, text} | {:error, message}
|
||||
@callback translate(text, to, from) :: {:ok, text} | {:error, message}
|
||||
end
|
31
lib/translator/direction/direction.ex
Normal file
31
lib/translator/direction/direction.ex
Normal file
@ -0,0 +1,31 @@
|
||||
defmodule Localizator.Translator.Direction do
|
||||
alias Localizator.Commons
|
||||
|
||||
defstruct [:from, :to]
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t() | Atom.t()
|
||||
@type from :: locale
|
||||
@type from_may_be_nil :: from | nil
|
||||
@type to :: locale
|
||||
@type direction :: {from, to} | to | %{from: from, to: to} | %{to: to}
|
||||
@type direction_struct :: %{to: to, from: from_may_be_nil}
|
||||
|
||||
@spec get(direction) :: direction_struct
|
||||
def get(direction) do
|
||||
direction_map =
|
||||
case direction do
|
||||
[from: from, to: to] -> %{to: "#{to}", from: "#{from}"}
|
||||
[from, to] -> %{to: "#{to}", from: "#{from}"}
|
||||
{from, to} -> %{to: "#{to}", from: "#{from}"}
|
||||
%{from: from, to: to} -> %{to: "#{to}", from: "#{from}"}
|
||||
%{to: to} -> %{to: "#{to}", from: nil}
|
||||
[to: to] -> %{to: "#{to}", from: nil}
|
||||
to -> %{to: "#{to}", from: nil}
|
||||
end
|
||||
|
||||
Commons.struct_from_map(direction_map, as: %__MODULE__{})
|
||||
end
|
||||
end
|
47
lib/translator/translator.ex
Normal file
47
lib/translator/translator.ex
Normal file
@ -0,0 +1,47 @@
|
||||
defmodule Localizator.Translator do
|
||||
alias Localizator.Translator.Direction
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
@type from :: locale
|
||||
@type from_may_be_nil :: from | nil
|
||||
@type to :: locale
|
||||
@type direction :: {from, to} | to | %{from: from, to: to} | %{to: to}
|
||||
@type direction_map :: %{to: to, from: from_may_be_nil}
|
||||
|
||||
@typedoc """
|
||||
Translation service
|
||||
"""
|
||||
@type translator :: Atom.t()
|
||||
|
||||
@typedoc """
|
||||
Translation services list
|
||||
"""
|
||||
@type translators :: [translator]
|
||||
|
||||
@typedoc """
|
||||
Translation resource
|
||||
"""
|
||||
@type resource :: String.t() | Map.t() | List.t()
|
||||
@type source :: resource
|
||||
@type result :: resource
|
||||
|
||||
@typedoc """
|
||||
Error Message
|
||||
"""
|
||||
@type message :: String.t()
|
||||
|
||||
@spec list() :: translators
|
||||
def list(), do: Application.get_env(:localizator, :translators)
|
||||
|
||||
@spec default() :: translator
|
||||
def default(), do: list() |> List.first()
|
||||
|
||||
@spec translate(source, direction, translator) :: {:ok, result} | {:error, message}
|
||||
def translate(source, direction, translator \\ default()) do
|
||||
map = Direction.get(direction)
|
||||
translator.translate(source, map.to, map.from)
|
||||
end
|
||||
end
|
40
lib/translator/yandex.ex
Normal file
40
lib/translator/yandex.ex
Normal file
@ -0,0 +1,40 @@
|
||||
defmodule Localizator.Translator.Yandex do
|
||||
@type text :: String.t()
|
||||
@type locale :: String.t()
|
||||
@type from :: locale
|
||||
@type to :: locale
|
||||
@type message :: String.t()
|
||||
|
||||
@behaviour Localizator.Translator.Base
|
||||
|
||||
@impl true
|
||||
@spec detect(text) :: {:ok, locale} | {:error, message}
|
||||
def detect(text) when is_bitstring(text) do
|
||||
case YandexTranslate.detect(text) do
|
||||
%{languageCode: locale} -> {:ok, locale}
|
||||
%{} -> {:error, "Couldn't detect language"}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec translate(text, to) :: {:ok, text} | {:error, message}
|
||||
def translate(text, to, from \\ nil)
|
||||
|
||||
def translate(text, to, from) when is_bitstring(text) and is_bitstring(to) do
|
||||
translator_response =
|
||||
case from do
|
||||
nil -> YandexTranslate.translate(text, to)
|
||||
_ -> YandexTranslate.translate(text, to, from)
|
||||
end
|
||||
|
||||
case translator_response do
|
||||
%{translations: translations} ->
|
||||
translations
|
||||
|> List.first()
|
||||
|> Map.fetch(:text)
|
||||
|
||||
%{message: message} ->
|
||||
{:error, message}
|
||||
end
|
||||
end
|
||||
end
|
28
lib/translitor/base.ex
Normal file
28
lib/translitor/base.ex
Normal file
@ -0,0 +1,28 @@
|
||||
defmodule Localizator.Translitor.Base do
|
||||
@moduledoc """
|
||||
Base translit module. Describing behaviour for transliteration modules
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
Plain Text
|
||||
"""
|
||||
@type text :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Translited Text
|
||||
"""
|
||||
@type translited :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Error Message
|
||||
"""
|
||||
@type message :: String.t()
|
||||
|
||||
@callback convert(text) :: {:ok, translited} | {:error, message}
|
||||
@callback locale() :: {:ok, locale}
|
||||
end
|
146
lib/translitor/be.ex
Normal file
146
lib/translitor/be.ex
Normal file
@ -0,0 +1,146 @@
|
||||
defmodule Localizator.Translitor.BE do
|
||||
@moduledoc """
|
||||
Translit module for Belorussian language.
|
||||
"""
|
||||
@locale "be"
|
||||
@table %{
|
||||
"А" => "A",
|
||||
"Б" => "B",
|
||||
"В" => "V",
|
||||
"Г" => "G",
|
||||
"Д" => "D",
|
||||
"Ж" => "Zh",
|
||||
"З" => "Z",
|
||||
"И" => "I",
|
||||
"І" => "I",
|
||||
"Й" => "J",
|
||||
"К" => "K",
|
||||
"Л" => "L",
|
||||
"М" => "M",
|
||||
"Н" => "N",
|
||||
"О" => "O",
|
||||
"П" => "P",
|
||||
"Р" => "R",
|
||||
"С" => "S",
|
||||
"Т" => "T",
|
||||
"У" => "U",
|
||||
"Ф" => "F",
|
||||
"Х" => "Kh",
|
||||
"Ц" => "Ts",
|
||||
"Ч" => "Ch",
|
||||
"Ш" => "Sh",
|
||||
"Щ" => "Shch",
|
||||
"Ы" => "Y",
|
||||
"Э" => "E",
|
||||
"Ь" => "",
|
||||
"Ъ" => "",
|
||||
"Ў" => "W",
|
||||
"а" => "a",
|
||||
"б" => "b",
|
||||
"в" => "v",
|
||||
"г" => "g",
|
||||
"д" => "d",
|
||||
"ж" => "zh",
|
||||
"з" => "z",
|
||||
"и" => "i",
|
||||
"і" => "i",
|
||||
"й" => "j",
|
||||
"к" => "k",
|
||||
"л" => "l",
|
||||
"м" => "m",
|
||||
"н" => "n",
|
||||
"о" => "o",
|
||||
"п" => "p",
|
||||
"р" => "r",
|
||||
"с" => "s",
|
||||
"т" => "t",
|
||||
"у" => "u",
|
||||
"ф" => "f",
|
||||
"х" => "kh",
|
||||
"ц" => "ts",
|
||||
"ч" => "ch",
|
||||
"ш" => "sh",
|
||||
"щ" => "shch",
|
||||
"ы" => "y",
|
||||
"э" => "e",
|
||||
"ў" => "w",
|
||||
"ь" => "",
|
||||
"ъ" => "",
|
||||
"'" => "",
|
||||
"’" => ""
|
||||
}
|
||||
|
||||
@typedoc """
|
||||
Plain Text
|
||||
"""
|
||||
@type text :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
|
||||
@behaviour Localizator.Translitor.Base
|
||||
|
||||
@impl true
|
||||
@spec locale() :: {:ok, locale}
|
||||
def locale(), do: @locale
|
||||
|
||||
@doc """
|
||||
A belorussian to english transliteration
|
||||
## Example
|
||||
``elixir
|
||||
iex> Localizator.Translitor.BE.convert("Ева Салаўёва")
|
||||
"Jeva Salawjova"
|
||||
``
|
||||
"""
|
||||
@impl true
|
||||
@spec convert(text) :: text
|
||||
def convert(text) when is_bitstring(text) do
|
||||
text
|
||||
|> String.graphemes()
|
||||
|> Enum.reduce([], &convert_letter/2)
|
||||
|> Enum.join()
|
||||
end
|
||||
|
||||
# special case for "<consonant>е", that should be mapped as "<translited consonant>ie"
|
||||
defp convert_letter(letter, acc)
|
||||
when letter in ~w(Е е Ё ё Ю ю Я я) do
|
||||
cond do
|
||||
List.last(acc) in ~w(B V G D Zh Z K L M N P R S T F Kh Ts Ch Sh Shch b v g d zh z k l m n p r s t f kh ts ch sh shch) ->
|
||||
acc ++ [convert_i(letter)]
|
||||
|
||||
List.last(acc) in (~w(A I J O U Y E W a i j o u y e w) ++ ["", " ", nil]) ->
|
||||
acc ++ [convert_j(letter)]
|
||||
end
|
||||
end
|
||||
|
||||
defp convert_letter(letter, acc) do
|
||||
acc ++ [convert_letter(letter)]
|
||||
end
|
||||
|
||||
defp convert_letter(letter) do
|
||||
case Map.get(@table, letter) do
|
||||
nil -> letter
|
||||
latin -> latin
|
||||
end
|
||||
end
|
||||
|
||||
defp convert_i("Е"), do: "Ie"
|
||||
defp convert_i("е"), do: "ie"
|
||||
defp convert_i("Ё"), do: "Io"
|
||||
defp convert_i("ё"), do: "io"
|
||||
defp convert_i("Ю"), do: "Iu"
|
||||
defp convert_i("ю"), do: "iu"
|
||||
defp convert_i("Я"), do: "Ia"
|
||||
defp convert_i("я"), do: "ia"
|
||||
|
||||
defp convert_j("Е"), do: "Je"
|
||||
defp convert_j("е"), do: "je"
|
||||
defp convert_j("Ё"), do: "Jo"
|
||||
defp convert_j("ё"), do: "jo"
|
||||
defp convert_j("Ю"), do: "Ju"
|
||||
defp convert_j("ю"), do: "ju"
|
||||
defp convert_j("Я"), do: "Ja"
|
||||
defp convert_j("я"), do: "ja"
|
||||
end
|
114
lib/translitor/ru.ex
Normal file
114
lib/translitor/ru.ex
Normal file
@ -0,0 +1,114 @@
|
||||
defmodule Localizator.Translitor.RU do
|
||||
@moduledoc """
|
||||
Translit module for Russian language. By ICAO Standard (2012—2016)
|
||||
"""
|
||||
@locale "ru"
|
||||
@table %{
|
||||
" " => " ",
|
||||
"А" => "A",
|
||||
"Б" => "B",
|
||||
"В" => "V",
|
||||
"Г" => "G",
|
||||
"Д" => "D",
|
||||
"Е" => "E",
|
||||
"Ё" => "E",
|
||||
"Ж" => "Zh",
|
||||
"З" => "Z",
|
||||
"И" => "I",
|
||||
"Й" => "I",
|
||||
"К" => "K",
|
||||
"Л" => "L",
|
||||
"М" => "M",
|
||||
"Н" => "N",
|
||||
"О" => "O",
|
||||
"П" => "P",
|
||||
"Р" => "R",
|
||||
"С" => "S",
|
||||
"Т" => "T",
|
||||
"У" => "U",
|
||||
"Ф" => "F",
|
||||
"Х" => "Kh",
|
||||
"Ц" => "Ts",
|
||||
"Ч" => "Ch",
|
||||
"Ш" => "Sh",
|
||||
"Щ" => "Shch",
|
||||
"Ъ" => "",
|
||||
"Ы" => "Y",
|
||||
"Ь" => "",
|
||||
"Э" => "E",
|
||||
"Ю" => "Iu",
|
||||
"Я" => "Ia",
|
||||
"а" => "a",
|
||||
"б" => "b",
|
||||
"в" => "v",
|
||||
"г" => "g",
|
||||
"д" => "d",
|
||||
"е" => "e",
|
||||
"ё" => "e",
|
||||
"ж" => "zh",
|
||||
"з" => "z",
|
||||
"и" => "i",
|
||||
"й" => "i",
|
||||
"к" => "k",
|
||||
"л" => "l",
|
||||
"м" => "m",
|
||||
"н" => "n",
|
||||
"о" => "o",
|
||||
"п" => "p",
|
||||
"р" => "r",
|
||||
"с" => "s",
|
||||
"т" => "t",
|
||||
"у" => "u",
|
||||
"ф" => "f",
|
||||
"х" => "kh",
|
||||
"ц" => "ts",
|
||||
"ч" => "ch",
|
||||
"ш" => "sh",
|
||||
"щ" => "shch",
|
||||
"ъ" => "",
|
||||
"ы" => "y",
|
||||
"ь" => "",
|
||||
"э" => "e",
|
||||
"ю" => "iu",
|
||||
"я" => "ia"
|
||||
}
|
||||
@typedoc """
|
||||
Plain Text
|
||||
"""
|
||||
@type text :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
|
||||
@behaviour Localizator.Translitor.Base
|
||||
|
||||
@impl true
|
||||
@spec locale() :: {:ok, locale}
|
||||
def locale(), do: @locale
|
||||
|
||||
@doc """
|
||||
A russian to english transliteration
|
||||
## Example
|
||||
``elixir
|
||||
iex> Localizator.Translitor.RU.convert("Роман Шевченко")
|
||||
"Roman Shevchenko"
|
||||
``
|
||||
"""
|
||||
@impl true
|
||||
@spec convert(text) :: text
|
||||
def convert(text) when is_bitstring(text) do
|
||||
text
|
||||
|> String.graphemes()
|
||||
|> Enum.map(&convert_letter/1)
|
||||
|> Enum.join()
|
||||
end
|
||||
|
||||
defp convert_letter(letter) do
|
||||
case Map.get(@table, letter) do
|
||||
nil -> letter
|
||||
latin -> latin
|
||||
end
|
||||
end
|
||||
end
|
47
lib/translitor/translitor.ex
Normal file
47
lib/translitor/translitor.ex
Normal file
@ -0,0 +1,47 @@
|
||||
defmodule Localizator.Translitor do
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
@type to :: locale
|
||||
|
||||
@typedoc """
|
||||
Translitor service
|
||||
"""
|
||||
@type translitor :: Atom.t()
|
||||
|
||||
@typedoc """
|
||||
Transliters list
|
||||
"""
|
||||
@type translitors :: [translitor]
|
||||
|
||||
@typedoc """
|
||||
Translition resource
|
||||
"""
|
||||
@type resource :: String.t() | Map.t() | List.t()
|
||||
@type source :: resource
|
||||
@type result :: resource
|
||||
|
||||
@typedoc """
|
||||
Error Message
|
||||
"""
|
||||
@type message :: String.t()
|
||||
|
||||
@spec list() :: translitors
|
||||
def list(), do: Application.get_env(:localizator, :translitors)
|
||||
|
||||
defp translitors() do
|
||||
list()
|
||||
|> Enum.map(fn translitor -> {translitor.locale(), translitor} end)
|
||||
|> Map.new()
|
||||
end
|
||||
|
||||
@spec convert(source, locale) :: {:ok, result} | {:error, message}
|
||||
def convert(source, locale) do
|
||||
translitor =
|
||||
translitors()
|
||||
|> Map.get(locale)
|
||||
|
||||
translitor.convert(source)
|
||||
end
|
||||
end
|
139
lib/translitor/uk.ex
Normal file
139
lib/translitor/uk.ex
Normal file
@ -0,0 +1,139 @@
|
||||
defmodule Localizator.Translitor.UK do
|
||||
@moduledoc """
|
||||
Translit module for Ukrainian language.
|
||||
"""
|
||||
@locale "uk"
|
||||
@table %{
|
||||
"А" => "A",
|
||||
"Б" => "B",
|
||||
"В" => "V",
|
||||
"Г" => "H",
|
||||
"Ґ" => "G",
|
||||
"Д" => "D",
|
||||
"Е" => "E",
|
||||
"Є" => "Ye",
|
||||
"Ж" => "Zh",
|
||||
"З" => "Z",
|
||||
"И" => "Y",
|
||||
"І" => "I",
|
||||
"Ї" => "Yi",
|
||||
"Й" => "Y",
|
||||
"К" => "K",
|
||||
"Л" => "L",
|
||||
"М" => "M",
|
||||
"Н" => "N",
|
||||
"О" => "O",
|
||||
"П" => "P",
|
||||
"Р" => "R",
|
||||
"С" => "S",
|
||||
"Т" => "T",
|
||||
"У" => "U",
|
||||
"Ф" => "F",
|
||||
"Х" => "Kh",
|
||||
"Ц" => "Ts",
|
||||
"Ч" => "Ch",
|
||||
"Ш" => "Sh",
|
||||
"Щ" => "Shch",
|
||||
"Ю" => "Yu",
|
||||
"Я" => "Ya",
|
||||
"а" => "a",
|
||||
"б" => "b",
|
||||
"в" => "v",
|
||||
"г" => "h",
|
||||
"ґ" => "g",
|
||||
"д" => "d",
|
||||
"е" => "e",
|
||||
"є" => "ie",
|
||||
"ж" => "zh",
|
||||
"з" => "z",
|
||||
"и" => "y",
|
||||
"і" => "i",
|
||||
"ї" => "i",
|
||||
"й" => "i",
|
||||
"к" => "k",
|
||||
"л" => "l",
|
||||
"м" => "m",
|
||||
"н" => "n",
|
||||
"о" => "o",
|
||||
"п" => "p",
|
||||
"р" => "r",
|
||||
"с" => "s",
|
||||
"т" => "t",
|
||||
"у" => "u",
|
||||
"ф" => "f",
|
||||
"х" => "kh",
|
||||
"ц" => "ts",
|
||||
"ч" => "ch",
|
||||
"ш" => "sh",
|
||||
"щ" => "shch",
|
||||
"ю" => "iu",
|
||||
"я" => "ia"
|
||||
}
|
||||
|
||||
@typedoc """
|
||||
Plain Text
|
||||
"""
|
||||
@type text :: String.t()
|
||||
|
||||
@typedoc """
|
||||
Locale
|
||||
"""
|
||||
@type locale :: String.t()
|
||||
|
||||
@behaviour Localizator.Translitor.Base
|
||||
|
||||
@impl true
|
||||
@spec locale() :: {:ok, locale}
|
||||
def locale(), do: @locale
|
||||
|
||||
@doc """
|
||||
A ukrainian to english transliteration
|
||||
## Example
|
||||
``elixir
|
||||
iex> Localizator.Translitor.UK.convert("Данило Негрієнко")
|
||||
"Danylo Nehriienko"
|
||||
``
|
||||
"""
|
||||
@impl true
|
||||
@spec convert(text) :: text
|
||||
def convert(text) when is_binary(text) do
|
||||
text
|
||||
|> String.graphemes()
|
||||
|> Enum.reduce([], &convert_letter/2)
|
||||
|> Enum.join()
|
||||
end
|
||||
|
||||
# special case for "Зг", that should be mapped as "zgh"
|
||||
defp convert_letter(letter, [last_letter] = acc)
|
||||
when letter in ~w(Г г) and last_letter in ~w(z Z) do
|
||||
acc ++ [convert_gh(letter)]
|
||||
end
|
||||
|
||||
# special case for "зг", that should be mapped as "zgh"
|
||||
defp convert_letter(letter, acc) when letter in ~w(Г г) do
|
||||
case List.last(acc) do
|
||||
last_letter when last_letter in ~w(z Z) -> acc ++ [convert_gh(letter)]
|
||||
_ -> acc ++ [convert_letter(letter)]
|
||||
end
|
||||
end
|
||||
|
||||
defp convert_letter(letter, acc) do
|
||||
acc ++ [convert_letter(letter)]
|
||||
end
|
||||
|
||||
defp convert_letter(" "), do: " "
|
||||
defp convert_letter("ь"), do: ""
|
||||
defp convert_letter("Ь"), do: ""
|
||||
defp convert_letter("'"), do: ""
|
||||
defp convert_letter("’"), do: ""
|
||||
|
||||
defp convert_letter(letter) do
|
||||
case Map.get(@table, letter) do
|
||||
nil -> letter
|
||||
latin -> latin
|
||||
end
|
||||
end
|
||||
|
||||
defp convert_gh("Г"), do: "Gh"
|
||||
defp convert_gh("г"), do: "gh"
|
||||
end
|
36
mix.exs
Normal file
36
mix.exs
Normal file
@ -0,0 +1,36 @@
|
||||
defmodule Localizator.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :localizator,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.10",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:yandex_translate, "~> 0.4.0"},
|
||||
|
||||
# Parsers/generators
|
||||
{:jason, "~> 1.2.1"},
|
||||
{:yaml_elixir, "~> 2.4.0"},
|
||||
|
||||
# Remix for autorestart
|
||||
{:ex_doc, "~> 0.21.3", only: :dev, runtime: false},
|
||||
{:credo, "~> 1.4.0-rc.1", only: :dev, runtime: false},
|
||||
{:remix, "~> 0.0.2", only: :dev}
|
||||
]
|
||||
end
|
||||
end
|
18
mix.lock
Normal file
18
mix.lock
Normal file
@ -0,0 +1,18 @@
|
||||
%{
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
"castore": {:hex, :castore, "0.1.6", "2da0dccb3eacb67841d11790598ff03cd5caee861e01fad61dce1376b5da28e6", [:mix], [], "hexpm", "f874c510b720d31dd6334e9ae5c859a06a3c9e67dfe1a195c512e57588556d3f"},
|
||||
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
|
||||
"earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
|
||||
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
|
||||
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
|
||||
"makeup": {:hex, :makeup, "1.0.2", "0b9f7bfb7a88bed961341b359bc2cc1b233517af891ba4890ec5a580ffe738b4", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "43833299231c6a6983afc75a34e43eeba638521d5527ff89809fa6372424fd7e"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
|
||||
"mint": {:hex, :mint, "1.0.0", "ca5ab33497ba2bdcc42f6cdd3927420a6159116be87c8173658e93c8746703da", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "b8943ef1e630879538dd6620bfc189d4d75fab3ad39f3fe9c50539879f7efd84"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
|
||||
"remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], [], "hexpm", "5f5555646ed4fca83fab8620735150aa0bc408c5a17a70d28cfa7086bc6f497c"},
|
||||
"yamerl": {:hex, :yamerl, "0.8.0", "8214cfe16bbabe5d1d6c14a14aea11c784b9a21903dd6a7c74f8ce180adae5c7", [:rebar3], [], "hexpm", "010634477bf9c208a0767dcca89116c2442cf0b5e87f9c870f85cd1c3e0c2aab"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.4.0", "2f444abc3c994c902851fde56b6a9cb82895c291c05a0490a289035c2e62ae71", [:mix], [{:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "4e25a6d5c873e393689c6f1062c5ec90f6cd1be2527b073178ae37eae4c78bee"},
|
||||
"yandex_translate": {:hex, :yandex_translate, "0.4.0", "a8851bdd0899d29334c9028aecc70c9921cae5dff86e512e30e72a0bceb6613a", [:mix], [{:castore, "~> 0.1.5", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.2.0", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.2.0", [hex: :joken, repo: "hexpm", optional: false]}, {:mint, "~> 1.0.0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "d4cdb333d8e447d0de20e0314ffc5fdd0a38421800866801007baca9102936f1"},
|
||||
}
|
66
test/data/translitor/be.txt
Normal file
66
test/data/translitor/be.txt
Normal file
@ -0,0 +1,66 @@
|
||||
Ева - Jeva
|
||||
Васiльева - Vasiljeva
|
||||
Васiлёнак - Vasilionak
|
||||
Ёрш - Jorsh
|
||||
Вераб’ёў - Vierabjow
|
||||
Салаўёва - Salawjova
|
||||
Адъютантов - Adjutantov
|
||||
Любоў - Liubow
|
||||
В’юноў - Vjunow
|
||||
Чарняк - Charniak
|
||||
Лябецкая - Liabietskaja
|
||||
Дар’я - Darja
|
||||
Азаранка - Azaranka
|
||||
Аліна - Alina
|
||||
Багушэвіч - Bagushevich
|
||||
Барыс - Barys
|
||||
Вольскі - Volski
|
||||
Віктар - Viktar
|
||||
Галубовіч - Galubovich
|
||||
Загорскі - Zagorski
|
||||
Галіна - Galina
|
||||
Дубко - Dubko
|
||||
Дзмітры - Dzmitry
|
||||
Дзмітрыева - Dzmitryjeva
|
||||
Емяльянаў - Jemialjanaw
|
||||
Жылуновіч - Zhylunovich
|
||||
Жанна - Zhanna
|
||||
Завальнюк - Zavalniuk
|
||||
Зарына - Zaryna
|
||||
Іваноўскі - Ivanowski
|
||||
Ірына - Iryna
|
||||
Шаройка - Sharojka
|
||||
Красько - Krasko
|
||||
Кацярына - Katsiaryna
|
||||
Лінкевіч - Linkievich
|
||||
Людміла - Liudmila
|
||||
Масько - Masko
|
||||
Марына - Maryna
|
||||
Насовіч - Nasovich
|
||||
Наталля - Natallia
|
||||
Осцікавіч - Ostsikavich
|
||||
Пятроўскі - Piatrowski
|
||||
Павел - Paviel
|
||||
Рыжонак - Ryzhonak
|
||||
Раіса - Raisa
|
||||
Смулькевіч - Smulkievich
|
||||
Сяргей - Siargiej
|
||||
Татарчук - Tatarchuk
|
||||
Тамара - Tamara
|
||||
Улашчык - Ulashchyk
|
||||
Уладзімір - Uladzimir
|
||||
Каўшоў - Kawshow
|
||||
Станіслаў - Stanislaw
|
||||
Сафонаў - Safonaw
|
||||
Фёдар - Fiodar
|
||||
Харытончык - Kharytonchyk
|
||||
Цвірка - Tsvirka
|
||||
Цімафей - Tsimafiej
|
||||
Чарнoвіч - Charnovich
|
||||
Часлаў - Chaslaw
|
||||
Шашковіч - Shashkovich
|
||||
Кашулёнак - Kashulionak
|
||||
Кныровіч - Knyrovich
|
||||
Валеры - Valiery
|
||||
Эйсмант - Ejsmant
|
||||
Эрык - Eryk
|
2
test/data/translitor/ru.txt
Normal file
2
test/data/translitor/ru.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Роман - Roman
|
||||
Шевченко - Shevchenko
|
79
test/data/translitor/uk.txt
Normal file
79
test/data/translitor/uk.txt
Normal file
@ -0,0 +1,79 @@
|
||||
Розгон - Rozghon
|
||||
Згурський - Zghurskyi
|
||||
Алушта - Alushta
|
||||
Андрій - Andrii
|
||||
Борщагівка - Borshchahivka
|
||||
Борисенко - Borysenko
|
||||
Вінниця - Vinnytsia
|
||||
Володимир - Volodymyr
|
||||
Гадяч - Hadiach
|
||||
Богдан - Bohdan
|
||||
Згурський - Zghurskyi
|
||||
Згори - Zghory
|
||||
Ґалаґан - Galagan
|
||||
Ґорґани - Gorgany
|
||||
Донецьк - Donetsk
|
||||
Дмитро - Dmytro
|
||||
Рівне - Rivne
|
||||
Олег - Oleh
|
||||
Есмань - Esman
|
||||
Єнакієве - Yenakiieve
|
||||
Гаєвич - Haievych
|
||||
Короп'є - Koropie
|
||||
Житомир - Zhytomyr
|
||||
Жанна - Zhanna
|
||||
Жежелів - Zhezheliv
|
||||
Закарпаття - Zakarpattia
|
||||
Казимирчук - Kazymyrchuk
|
||||
Медвин - Medvyn
|
||||
Михайленко - Mykhailenko
|
||||
Іванків - Ivankiv
|
||||
Іващенко - Ivashchenko
|
||||
Їжакевич - Yizhakevych
|
||||
Кадиївка - Kadyivka
|
||||
Мар'їне - Marine
|
||||
Йосипівна - Yosypivna
|
||||
Стрий - Stryi
|
||||
Олексій - Oleksii
|
||||
Київ - Kyiv
|
||||
Коваленко - Kovalenko
|
||||
Лебедин - Lebedyn
|
||||
Леонід - Leonid
|
||||
Миколаїв - Mykolaiv
|
||||
Маринич - Marynych
|
||||
Ніжин - Nizhyn
|
||||
Наталія - Nataliia
|
||||
Одеса - Odesa
|
||||
Онищенко - Onyshchenko
|
||||
Полтава - Poltava
|
||||
Петро - Petro
|
||||
Розгон - Rozghon
|
||||
Решетилівка - Reshetylivka
|
||||
Рибчинський - Rybchynskyi
|
||||
Суми - Sumy
|
||||
Соломія - Solomiia
|
||||
Тернопіль - Ternopil
|
||||
Троць - Trots
|
||||
Ужгород - Uzhhorod
|
||||
Уляна - Uliana
|
||||
Фастів - Fastiv
|
||||
Філіпчук - Filipchuk
|
||||
Харків - Kharkiv
|
||||
Христина - Khrystyna
|
||||
Біла Церква - Bila Tserkva
|
||||
Стеценко - Stetsenko
|
||||
Чернівці - Chernivtsi
|
||||
Шевченко - Shevchenko
|
||||
Шостка - Shostka
|
||||
Кишеньки - Kyshenky
|
||||
Щербухи - Shcherbukhy
|
||||
Гоща - Hoshcha
|
||||
Гаращенко - Harashchenko
|
||||
Юрій - Yurii
|
||||
Корюківка - Koriukivka
|
||||
Яготин - Yahotyn
|
||||
Ярошенко - Yaroshenko
|
||||
Костянтин - Kostiantyn
|
||||
Знам'янка - Znamianka
|
||||
Феодосія - Feodosiia
|
||||
Смартphone - Smartphone
|
8
test/localizator_test.exs
Normal file
8
test/localizator_test.exs
Normal file
@ -0,0 +1,8 @@
|
||||
defmodule LocalizatorTest do
|
||||
use ExUnit.Case
|
||||
doctest Localizator
|
||||
|
||||
test "greets the world" do
|
||||
assert Localizator.hello() == :world
|
||||
end
|
||||
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
@ -0,0 +1 @@
|
||||
ExUnit.start()
|
22
test/translitor_test/be_test.exs
Normal file
22
test/translitor_test/be_test.exs
Normal file
@ -0,0 +1,22 @@
|
||||
defmodule Localizator.TranslitorTest.BETest do
|
||||
alias Localizator.Translitor.BE
|
||||
use ExUnit.Case
|
||||
doctest Localizator.Translitor.BE
|
||||
|
||||
test "Sucess Belorussian translitaration" do
|
||||
data = file_to_list("test/data/translitor/be.txt")
|
||||
|
||||
Enum.each(data, fn words ->
|
||||
assert [cyrillic, latin] = String.split(words, " - "),
|
||||
"Invalid input for tests. Expected two words separated by dash, got: #{words}"
|
||||
|
||||
assert latin == BE.convert(cyrillic)
|
||||
end)
|
||||
end
|
||||
|
||||
defp file_to_list(file) do
|
||||
file
|
||||
|> File.read!()
|
||||
|> String.split("\n")
|
||||
end
|
||||
end
|
22
test/translitor_test/ru_test.exs
Normal file
22
test/translitor_test/ru_test.exs
Normal file
@ -0,0 +1,22 @@
|
||||
defmodule Localizator.TranslitorTest.RUTest do
|
||||
alias Localizator.Translitor.RU
|
||||
use ExUnit.Case
|
||||
doctest Localizator.Translitor.RU
|
||||
|
||||
test "Sucess Russian translitaration" do
|
||||
data = file_to_list("test/data/translitor/ru.txt")
|
||||
|
||||
Enum.each(data, fn words ->
|
||||
assert [cyrillic, latin] = String.split(words, " - "),
|
||||
"Invalid input for tests. Expected two words separated by dash, got: #{words}"
|
||||
|
||||
assert latin == RU.convert(cyrillic)
|
||||
end)
|
||||
end
|
||||
|
||||
defp file_to_list(file) do
|
||||
file
|
||||
|> File.read!()
|
||||
|> String.split("\n")
|
||||
end
|
||||
end
|
22
test/translitor_test/uk_test.exs
Normal file
22
test/translitor_test/uk_test.exs
Normal file
@ -0,0 +1,22 @@
|
||||
defmodule Localizator.TranslitorTest.UKTest do
|
||||
alias Localizator.Translitor.UK
|
||||
use ExUnit.Case
|
||||
doctest Localizator.Translitor.UK
|
||||
|
||||
test "Sucess Ukrainian translitaration" do
|
||||
data = file_to_list("test/data/translitor/uk.txt")
|
||||
|
||||
Enum.each(data, fn words ->
|
||||
assert [cyrillic, latin] = String.split(words, " - "),
|
||||
"Invalid input for tests. Expected two words separated by dash, got: #{words}"
|
||||
|
||||
assert latin == UK.convert(cyrillic)
|
||||
end)
|
||||
end
|
||||
|
||||
defp file_to_list(file) do
|
||||
file
|
||||
|> File.read!()
|
||||
|> String.split("\n")
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user