Packages

BeamLabCountries is a collection of all sorts of useful information for every country in the [ISO 3166](https://wikipedia.org/wiki/ISO_3166) standard. It includes country data, subdivisions (states/provinces), international organizations, language information, and country name translations.

Current section

Files

Jump to
beamlab_countries lib languages.ex
Raw

lib/languages.ex

defmodule BeamLabCountries.Languages do
@moduledoc """
Module for looking up language names from ISO 639-1 codes and regional locales.
This module provides two levels of language data:
1. **Base Languages** - ISO 639-1 codes (e.g., "en", "es", "de") with 184 languages
2. **Locales** - Regional variants (e.g., "en-US", "es-ES", "pt-BR") combining language and country
## Base Language Examples
iex> BeamLabCountries.Languages.get_name("en")
"English"
iex> BeamLabCountries.Languages.get("de")
%BeamLabCountries.Language{code: "de", name: "German", native_name: "Deutsch", family: "Indo-European"}
## Locale Examples
locale = BeamLabCountries.Languages.get_locale("en-US")
locale.name
#=> "English (United States)"
BeamLabCountries.Languages.all_locales() |> length()
#=> 140
## Country-Language Associations
BeamLabCountries.Languages.country_names_for_language("en")
#=> ["United States of America", "United Kingdom", ...]
"""
alias BeamLabCountries.Language
alias BeamLabCountries.Locale
# Load base languages from JSON at compile time
@languages_path Path.join([:code.priv_dir(:beamlab_countries), "data", "languages.json"])
@external_resource @languages_path
@languages @languages_path
|> File.read!()
|> JSON.decode!()
# Load locales from JSON at compile time
@locales_path Path.join([:code.priv_dir(:beamlab_countries), "data", "locales.json"])
@external_resource @locales_path
@raw_locales @locales_path
|> File.read!()
|> JSON.decode!()
# Build country lookup for enriching locales with flag and country_name
# We need to defer this to runtime since BeamLabCountries may not be compiled yet
defp get_country(code) do
BeamLabCountries.get(code)
end
# Build locales with enriched data (flag, country_name from Country)
@locales @raw_locales
|> Enum.map(fn {code, data} ->
{code,
%{
"code" => code,
"base" => data["base"],
"region" => data["region"],
"name" => data["name"],
"nativeName" => data["nativeName"]
}}
end)
|> Map.new()
# Build index of locale codes by base language code
@locales_by_base @raw_locales
|> Enum.group_by(fn {_code, data} -> data["base"] end, fn {code, _data} ->
code
end)
# ============================================================================
# Base Language Functions (existing API - backward compatible)
# ============================================================================
@doc """
Returns the English name for a language code.
## Examples
iex> BeamLabCountries.Languages.get_name("en")
"English"
iex> BeamLabCountries.Languages.get_name("de")
"German"
iex> BeamLabCountries.Languages.get_name("ja")
"Japanese"
"""
@spec get_name(String.t()) :: String.t() | nil
def get_name(code) when is_binary(code) do
case Map.get(@languages, String.downcase(code)) do
nil -> nil
data -> data["name"]
end
end
@doc """
Returns the native name for a language code.
## Examples
iex> BeamLabCountries.Languages.get_native_name("en")
"English"
iex> BeamLabCountries.Languages.get_native_name("de")
"Deutsch"
iex> BeamLabCountries.Languages.get_native_name("ja")
"ζ—₯本θͺž (にほんご)"
"""
@spec get_native_name(String.t()) :: String.t() | nil
def get_native_name(code) when is_binary(code) do
case Map.get(@languages, String.downcase(code)) do
nil -> nil
data -> data["nativeName"]
end
end
@doc """
Returns full language info as a Language struct for a language code.
## Examples
iex> BeamLabCountries.Languages.get("en")
%BeamLabCountries.Language{code: "en", name: "English", native_name: "English", family: "Indo-European"}
iex> BeamLabCountries.Languages.get("invalid")
nil
"""
@spec get(String.t()) :: Language.t() | nil
def get(code) when is_binary(code) do
case Map.get(@languages, String.downcase(code)) do
nil ->
nil
data ->
%Language{
code: data["639-1"],
name: data["name"],
native_name: data["nativeName"],
family: data["family"]
}
end
end
@doc """
Returns all languages as Language structs.
## Examples
iex> languages = BeamLabCountries.Languages.all()
iex> length(languages)
184
iex> %BeamLabCountries.Language{} = hd(languages)
"""
@spec all() :: [Language.t()]
def all do
@languages
|> Enum.map(fn {_code, data} ->
%Language{
code: data["639-1"],
name: data["name"],
native_name: data["nativeName"],
family: data["family"]
}
end)
|> Enum.sort_by(& &1.name)
end
@doc """
Returns all language codes.
## Examples
iex> "en" in BeamLabCountries.Languages.all_codes()
true
"""
@spec all_codes() :: [String.t()]
def all_codes do
Map.keys(@languages)
end
@doc """
Returns the count of supported base languages.
## Examples
iex> BeamLabCountries.Languages.count()
184
"""
@spec count() :: non_neg_integer()
def count do
map_size(@languages)
end
@doc """
Checks if a base language code is valid.
## Examples
iex> BeamLabCountries.Languages.valid?("en")
true
iex> BeamLabCountries.Languages.valid?("invalid")
false
"""
@spec valid?(String.t()) :: boolean()
def valid?(code) when is_binary(code) do
Map.has_key?(@languages, String.downcase(code))
end
# ============================================================================
# Locale Functions (new API)
# ============================================================================
@doc """
Returns a Locale struct for the given locale code.
Locale codes can be either:
- Full locale codes like "en-US", "es-ES", "pt-BR"
- Base language codes like "ja", "ko", "ar" (for languages without regional variants in the data)
The flag and country_name are derived from the Country data based on the region code.
## Examples
iex> locale = BeamLabCountries.Languages.get_locale("en-US")
iex> locale.code
"en-US"
iex> locale.name
"English (United States)"
iex> locale.flag
"πŸ‡ΊπŸ‡Έ"
iex> BeamLabCountries.Languages.get_locale("EN-us").code
"en-US"
iex> BeamLabCountries.Languages.get_locale("invalid")
nil
"""
@spec get_locale(String.t()) :: Locale.t() | nil
def get_locale(code) when is_binary(code) do
case Map.get(@locales, normalize_locale_code(code)) do
nil ->
nil
data ->
country = get_country(data["region"])
%Locale{
code: data["code"],
base_code: data["base"],
region_code: data["region"],
name: data["name"],
native_name: data["nativeName"],
flag: country && country.flag,
country_name: country && country.name,
continent: country && country.continent,
region: country && country.region,
subregion: country && country.subregion
}
end
end
@doc """
Returns all locales as Locale structs, sorted by name.
## Examples
iex> locales = BeamLabCountries.Languages.all_locales()
iex> length(locales) > 0
true
iex> %BeamLabCountries.Locale{} = hd(locales)
"""
@spec all_locales() :: [Locale.t()]
def all_locales do
@locales
|> Enum.map(fn {_code, data} ->
country = get_country(data["region"])
%Locale{
code: data["code"],
base_code: data["base"],
region_code: data["region"],
name: data["name"],
native_name: data["nativeName"],
flag: country && country.flag,
country_name: country && country.name,
continent: country && country.continent,
region: country && country.region,
subregion: country && country.subregion
}
end)
|> Enum.sort_by(& &1.name)
end
@doc """
Returns all locale codes.
## Examples
iex> "en-US" in BeamLabCountries.Languages.all_locale_codes()
true
"""
@spec all_locale_codes() :: [String.t()]
def all_locale_codes do
Map.keys(@locales)
end
@doc """
Returns the count of supported locales.
## Examples
iex> BeamLabCountries.Languages.locale_count()
140
"""
@spec locale_count() :: non_neg_integer()
def locale_count do
map_size(@locales)
end
@doc """
Returns all locales for a given base language code.
## Examples
iex> locales = BeamLabCountries.Languages.locales_for_language("en")
iex> Enum.map(locales, & &1.code)
["en-AU", "en-CA", "en-GB", "en-US"]
iex> locales = BeamLabCountries.Languages.locales_for_language("es")
iex> length(locales)
4
"""
@spec locales_for_language(String.t()) :: [Locale.t()]
def locales_for_language(base_code) when is_binary(base_code) do
locale_codes = Map.get(@locales_by_base, String.downcase(base_code), [])
locale_codes
|> Enum.map(&get_locale/1)
|> Enum.reject(&is_nil/1)
|> Enum.sort_by(& &1.name)
end
@doc """
Checks if a locale code is valid.
## Examples
iex> BeamLabCountries.Languages.valid_locale?("en-US")
true
iex> BeamLabCountries.Languages.valid_locale?("EN-us")
true
iex> BeamLabCountries.Languages.valid_locale?("invalid")
false
"""
@spec valid_locale?(String.t()) :: boolean()
def valid_locale?(code) when is_binary(code) do
Map.has_key?(@locales, normalize_locale_code(code))
end
@doc """
Parses a locale code into its base language code and region code.
## Examples
iex> BeamLabCountries.Languages.parse_locale("en-US")
{"en", "US"}
iex> BeamLabCountries.Languages.parse_locale("pt-BR")
{"pt", "BR"}
iex> BeamLabCountries.Languages.parse_locale("ja")
{"ja", nil}
iex> BeamLabCountries.Languages.parse_locale("EN-us")
{"en", "US"}
"""
@spec parse_locale(String.t()) :: {String.t(), String.t() | nil}
def parse_locale(code) when is_binary(code) do
case String.split(code, "-", parts: 2) do
[base, region] -> {String.downcase(base), String.upcase(region)}
[base] -> {String.downcase(base), nil}
end
end
# Normalizes a locale code to the canonical "base-REGION" form used
# as keys in the locales data (e.g. "EN-us" -> "en-US", "JA" -> "ja").
defp normalize_locale_code(code) do
case parse_locale(code) do
{base, nil} -> base
{base, region} -> base <> "-" <> region
end
end
# ============================================================================
# Country-Language Association Functions
# ============================================================================
@doc """
Returns all countries where a language is spoken.
Uses the `languages_spoken` field from Country data.
## Examples
iex> countries = BeamLabCountries.Languages.countries_for_language("en")
iex> length(countries) > 50
true
iex> "United States of America" in Enum.map(countries, & &1.name)
true
"""
@spec countries_for_language(String.t()) :: [BeamLabCountries.Country.t()]
def countries_for_language(lang_code) when is_binary(lang_code) do
code = String.downcase(lang_code)
BeamLabCountries.all()
|> Enum.filter(fn country ->
languages = country.languages_spoken || []
code in Enum.map(languages, &String.downcase/1)
end)
|> Enum.sort_by(& &1.name)
end
@doc """
Returns country names where a language is spoken.
## Examples
iex> names = BeamLabCountries.Languages.country_names_for_language("en")
iex> "United States of America" in names
true
iex> "United Kingdom of Great Britain and Northern Ireland" in names
true
"""
@spec country_names_for_language(String.t()) :: [String.t()]
def country_names_for_language(lang_code) when is_binary(lang_code) do
lang_code
|> countries_for_language()
|> Enum.map(& &1.name)
end
@doc """
Returns flags for countries where a language is spoken.
## Examples
iex> flags = BeamLabCountries.Languages.flags_for_language("en")
iex> "πŸ‡ΊπŸ‡Έ" in flags
true
iex> "πŸ‡¬πŸ‡§" in flags
true
"""
@spec flags_for_language(String.t()) :: [String.t()]
def flags_for_language(lang_code) when is_binary(lang_code) do
lang_code
|> countries_for_language()
|> Enum.map(& &1.flag)
|> Enum.reject(&is_nil/1)
end
end