Packages

phoenix_kit

1.7.74
1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules languages dialect_mapper.ex
Raw

lib/modules/languages/dialect_mapper.ex

defmodule PhoenixKit.Modules.Languages.DialectMapper do
@moduledoc """
Handles mapping between base language codes (en, es) and full dialect codes (en-US, es-MX).
This module provides the core logic for PhoenixKit's simplified URL architecture where
URLs show base codes (/en/) but translations use full dialect codes (en-US).
## Architecture
PhoenixKit uses a two-tier locale system:
1. **Base Language Codes** - Used in URLs for simplicity
- Format: 2-letter ISO 639-1 codes (en, es, fr, de, pt, zh, ja, etc.)
- Examples: `/en/dashboard`, `/es/admin`, `/fr/users`
- User-facing, SEO-friendly, easy to remember
2. **Full Dialect Codes** - Used internally for translations
- Format: BCP 47 language tags (en-US, es-MX, pt-BR, zh-CN)
- Examples: en-US, en-GB, es-ES, es-MX, pt-PT, pt-BR
- Translation-aware, respects regional differences
## Data Flow
```
User visits: /en/dashboard
↓
Extract base: "en"
↓
Resolve dialect: "en-US" (default) or user.custom_fields["preferred_locale"] ("en-GB")
↓
Set Gettext: "en-US" or "en-GB"
↓
Generate URLs: Always use base code "en"
```
## Default Dialect Mapping
When no user preference exists, base codes map to most common regional variants:
- `en` → `en-US` (American English)
- `es` → `es-ES` (European Spanish)
- `pt` → `pt-BR` (Brazilian Portuguese)
- `zh` → `zh-CN` (Simplified Chinese)
- `de` → `de-DE` (German Germany)
- `fr` → `fr-FR` (French France)
## User Preferences
Authenticated users can override default mappings:
- User prefers British English: sets `custom_fields["preferred_locale"]` = "en-GB"
- Visits `/en/dashboard`
- System uses "en-GB" for translations
- URLs remain `/en/` (not `/en-GB/`)
## Examples
# Extract base language from full dialect
iex> DialectMapper.extract_base("en-US")
"en"
iex> DialectMapper.extract_base("es-MX")
"es"
# Convert base to default dialect
iex> DialectMapper.base_to_dialect("en")
"en-US"
iex> DialectMapper.base_to_dialect("pt")
"pt-BR"
# Resolve dialect with user preference (stored in custom_fields)
iex> user = %User{custom_fields: %{"preferred_locale" => "en-GB"}}
iex> DialectMapper.resolve_dialect("en", user)
"en-GB"
iex> DialectMapper.resolve_dialect("en", nil)
"en-US"
## Validation
iex> DialectMapper.valid_base_code?("en")
true
iex> DialectMapper.valid_base_code?("xx")
false
## Getting Available Dialects
iex> DialectMapper.dialects_for_base("en")
["en-US", "en-GB", "en-CA", "en-AU"]
iex> DialectMapper.dialects_for_base("es")
["es-ES", "es-MX", "es-AR", "es-CO"]
"""
alias PhoenixKit.Modules.Languages
# Default dialect mapping for most common variants
# Based on usage statistics and regional population
@default_dialects %{
"en" => "en-US",
# English
"es" => "es-ES",
# Spanish
"fr" => "fr-FR",
# French
"de" => "de-DE",
# German
"pt" => "pt-BR",
# Portuguese (Brazilian Portuguese more common)
"zh" => "zh-CN",
# Chinese (Simplified more common)
# Languages without regional variants map to themselves
"ar" => "ar",
# Arabic
"ja" => "ja",
# Japanese
"ko" => "ko",
# Korean
"it" => "it",
# Italian
"ru" => "ru",
# Russian
"hi" => "hi",
# Hindi
"bn" => "bn",
# Bengali
"pa" => "pa",
# Punjabi
"jv" => "jv",
# Javanese
"vi" => "vi",
# Vietnamese
"tr" => "tr",
# Turkish
"pl" => "pl",
# Polish
"uk" => "uk",
# Ukrainian
"th" => "th",
# Thai
"nl" => "nl",
# Dutch
"sv" => "sv",
# Swedish
"no" => "no",
# Norwegian
"da" => "da",
# Danish
"fi" => "fi",
# Finnish
"cs" => "cs",
# Czech
"hu" => "hu",
# Hungarian
"ro" => "ro",
# Romanian
"el" => "el",
# Greek
"he" => "he",
# Hebrew
"id" => "id",
# Indonesian
"ms" => "ms",
# Malay
"fa" => "fa",
# Persian
"sw" => "sw",
# Swahili
"ta" => "ta",
# Tamil
"te" => "te",
# Telugu
"mr" => "mr",
# Marathi
"ur" => "ur",
# Urdu
"gu" => "gu",
# Gujarati
"kn" => "kn",
# Kannada
"ml" => "ml"
# Malayalam
}
@doc """
Extracts base language code from full dialect code.
Splits on hyphen and returns first part (lowercased).
Handles both dialect codes (en-US) and base codes (en).
Returns "en" as default fallback for nil and empty string values.
## Examples
iex> DialectMapper.extract_base("en-US")
"en"
iex> DialectMapper.extract_base("es-MX")
"es"
iex> DialectMapper.extract_base("zh-Hans-CN")
"zh"
iex> DialectMapper.extract_base("ja")
"ja"
iex> DialectMapper.extract_base("EN-GB")
"en"
iex> DialectMapper.extract_base(nil)
"en"
iex> DialectMapper.extract_base("")
"en"
"""
# Default fallback for nil and empty strings
def extract_base(nil), do: "en"
def extract_base(""), do: "en"
def extract_base(locale) when is_binary(locale) do
locale
|> String.split("-")
|> List.first()
|> String.downcase()
end
@doc """
Converts base language code to default dialect.
Uses predefined mapping for most common regional variants.
Falls back to base code if no mapping exists.
## Examples
iex> DialectMapper.base_to_dialect("en")
"en-US"
iex> DialectMapper.base_to_dialect("pt")
"pt-BR"
iex> DialectMapper.base_to_dialect("ja")
"ja"
iex> DialectMapper.base_to_dialect("xx")
"xx"
"""
def base_to_dialect(base_code) when is_binary(base_code) do
base_lower = String.downcase(base_code)
Map.get(@default_dialects, base_lower, base_lower)
end
@doc """
Resolves the full dialect code for a user visiting a base language URL.
Resolution priority:
1. User's saved preference (if authenticated and preference matches base code)
2. Default dialect mapping for that base language
## Examples
iex> user = %User{custom_fields: %{"preferred_locale" => "en-GB"}}
iex> DialectMapper.resolve_dialect("en", user)
"en-GB"
iex> user = %User{custom_fields: %{"preferred_locale" => "es-MX"}}
iex> DialectMapper.resolve_dialect("en", user)
"en-US" # Preference doesn't match base, use default
iex> DialectMapper.resolve_dialect("en", nil)
"en-US"
iex> guest = %{some_field: "value"}
iex> DialectMapper.resolve_dialect("es", guest)
"es-ES"
## Security
User preference only applied if it matches the requested base code.
This prevents users from forcing unintended locales via preference tampering.
## Graceful Degradation
If user preference becomes invalid (dialect disabled, typo, etc.),
system falls back to default mapping. No crashes or errors.
"""
def resolve_dialect(base_code, user \\ nil)
def resolve_dialect(base_code, %{custom_fields: %{"preferred_locale" => preferred}} = _user)
when is_binary(preferred) do
# Verify user's preference matches the base code in URL
# Security: prevents locale preference injection attacks
if extract_base(preferred) == String.downcase(base_code) do
preferred
else
base_to_dialect(base_code)
end
end
def resolve_dialect(base_code, _user) do
base_to_dialect(base_code)
end
@doc """
Validates if a base language code is supported.
Checks if the default dialect for this base code exists in the
predefined language list.
## Examples
iex> DialectMapper.valid_base_code?("en")
true
iex> DialectMapper.valid_base_code?("ja")
true
iex> DialectMapper.valid_base_code?("xx")
false
iex> DialectMapper.valid_base_code?("en-US")
false # Not a base code (contains hyphen)
## Notes
- Only validates base codes (2 letters)
- Full dialect codes will return false (use extract_base first)
- Checks against Languages.get_predefined_language/1
"""
def valid_base_code?(base_code) when is_binary(base_code) do
# Only validate if it looks like a base code (2 letters, no hyphen)
if String.length(base_code) == 2 and not String.contains?(base_code, "-") do
dialect = base_to_dialect(base_code)
Languages.get_predefined_language(dialect) != nil
else
false
end
end
@doc """
Gets all available dialect codes for a base language.
Searches the predefined language list for all dialects
matching the given base code.
## Examples
iex> DialectMapper.dialects_for_base("en")
["en-US", "en-GB", "en-CA", "en-AU"]
iex> DialectMapper.dialects_for_base("es")
["es-ES", "es-MX", "es-AR", "es-CO"]
iex> DialectMapper.dialects_for_base("ja")
["ja"]
iex> DialectMapper.dialects_for_base("xx")
[]
## Use Cases
- Populate user preference dropdown
- Admin analytics (dialects per base language)
- Migration tools (find affected users)
"""
def dialects_for_base(base_code) when is_binary(base_code) do
base_lower = String.downcase(base_code)
Languages.get_available_languages()
|> Enum.filter(fn %{code: code} ->
extract_base(code) == base_lower
end)
|> Enum.map(& &1.code)
|> Enum.sort()
end
@doc """
Gets the default dialects map.
Useful for debugging, testing, or documentation purposes.
## Examples
iex> defaults = DialectMapper.default_dialects()
iex> defaults["en"]
"en-US"
iex> defaults["pt"]
"pt-BR"
"""
def default_dialects, do: @default_dialects
end