defmodule Whatlangex do
@moduledoc """
NIF bindings for Whatlang, a natural language detection library written in Rust.
"""
use Rustler, otp_app: :whatlangex, crate: "whatlang_nif"
defmodule Detection do
@moduledoc """
Struct representing a language detection result.
"""
@type t :: %__MODULE__{
lang: String.t(),
script: String.t(),
confidence: float
}
defstruct [:lang, :script, :confidence]
end
defmodule DetectOpts do
@moduledoc """
Options struct and keywords schema for the `detect` function.
"""
@type t :: %__MODULE__{
allowlist: [String.t()] | nil,
denylist: [String.t()] | nil
}
defstruct allowlist: nil, denylist: nil
@keywords_schema [
allowlist: [
doc: "List of language codes to consider (e.g., `[\"eng\", \"fra\", \"spa\"]`)",
type: {:or, [nil, {:list, :string}]},
default: nil
],
denylist: [
doc: "List of language codes to exclude (e.g., `[\"rus\", \"ukr\"]`)",
type: {:or, [nil, {:list, :string}]},
default: nil
]
]
def validate_keywords!(opts) do
NimbleOptions.validate!(opts, @keywords_schema)
end
def from_keywords!(opts) do
validated_opts = DetectOpts.validate_keywords!(opts)
%DetectOpts{
allowlist: validated_opts[:allowlist],
denylist: validated_opts[:denylist]
}
end
def docs_for_keywords do
NimbleOptions.docs(@keywords_schema)
end
end
@doc """
Detect the language of the given sentence.
## Examples
iex> detect("This is a cool sentence.")
%Whatlangex.Detection{lang: "eng", script: "Latin", confidence: ...}
iex> detect("")
nil
"""
@spec detect(String.t()) :: Detection.t() | nil
def detect(sentence) do
nif_detect(sentence, %DetectOpts{})
end
@doc """
Detect the language of the given sentence with filtering options.
## Options
#{DetectOpts.docs_for_keywords()}
Note: `allowlist` and `denylist` are mutually exclusive. If both are provided, only `allowlist` will be used.
## Examples
iex> detect("Ceci est une phrase.", allowlist: ["eng", "fra"])
%Whatlangex.Detection{lang: "fra", script: "Latin", confidence: ...}
iex> detect("Hello world", denylist: ["spa", "fra"])
%Whatlangex.Detection{lang: "eng", script: "Latin", confidence: ...}
iex> detect("", allowlist: ["eng"])
nil
"""
@spec detect(String.t(), keyword()) :: Detection.t() | nil
def detect(sentence, opts) when is_list(opts) do
opts = DetectOpts.from_keywords!(opts)
nif_detect(sentence, opts)
end
@doc """
Get the full language native name from language code.
## Examples
iex> code_to_name("fra")
"Français"
iex> code_to_name("abc")
nil
"""
@spec code_to_name(String.t()) :: String.t() | nil
def code_to_name(code) do
nif_code_to_name(code)
end
@doc """
Get the full language English name from language code.
## Examples
iex> code_to_eng_name("fra")
"French"
iex> code_to_eng_name("abc")
nil
"""
@spec code_to_eng_name(String.t()) :: String.t() | nil
def code_to_eng_name(code) do
nif_code_to_eng_name(code)
end
defp nif_detect(_sentence, _opts) do
error_if_not_nif_loaded()
end
defp nif_code_to_name(_code) do
error_if_not_nif_loaded()
end
defp nif_code_to_eng_name(_code) do
error_if_not_nif_loaded()
end
defp error_if_not_nif_loaded do
:erlang.nif_error(:nif_not_loaded)
end
end