Packages
phoenix_kit
1.7.114
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
221 Versions
Jump to
Current section
221 Versions
Compare versions
12
files changed
+411
additions
-423
deletions
unknownCHANGELOG.md
File is too large to be displayed (100 KB limit).
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/BeamLabEU/phoenix_kit">>}]}. |
| 2 2 | {<<"name">>,<<"phoenix_kit">>}. |
| 3 | - {<<"version">>,<<"1.7.113">>}. |
| 3 | + {<<"version">>,<<"1.7.114">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<65,32,102,111,117,110,100,97,116,105,111,110,32,102,111,114,32,98,117,105, |
| 6 6 | 108,100,105,110,103,32,69,108,105,120,105,114,32,80,104,111,101,110,105, |
| @@ -103,7 +103,7 @@ alias PhoenixKit.Modules.Languages.DialectMapper | |
| 103 103 | |
| 104 104 | DialectMapper.extract_base("en-US") # => "en" |
| 105 105 | DialectMapper.base_to_dialect("en") # => "en-US" |
| 106 | - DialectMapper.resolve_dialect("en", user) # Considers user.custom_fields["preferred_locale"] |
| 106 | + DialectMapper.resolve_dialect("en") # => "en-US" (URL-driven; base -> default dialect) |
| 107 107 | ``` |
| 108 108 | |
| 109 109 | ## Key API Functions |
| @@ -26,9 +26,9 @@ defmodule PhoenixKit.Modules.Languages.DialectMapper do | |
| 26 26 | ↓ |
| 27 27 | Extract base: "en" |
| 28 28 | ↓ |
| 29 | - Resolve dialect: "en-US" (default) or user.custom_fields["preferred_locale"] ("en-GB") |
| 29 | + Resolve dialect: "en" → "en-US" (default mapping for the base code) |
| 30 30 | ↓ |
| 31 | - Set Gettext: "en-US" or "en-GB" |
| 31 | + Set Gettext: "en-US" |
| 32 32 | ↓ |
| 33 33 | Generate URLs: Always use base code "en" |
| 34 34 | ``` |
| @@ -43,13 +43,13 @@ defmodule PhoenixKit.Modules.Languages.DialectMapper do | |
| 43 43 | - `de` → `de-DE` (German Germany) |
| 44 44 | - `fr` → `fr-FR` (French France) |
| 45 45 | |
| 46 | - ## User Preferences |
| 46 | + ## URL-Driven Resolution |
| 47 47 | |
| 48 | - Authenticated users can override default mappings: |
| 49 | - - User prefers British English: sets `custom_fields["preferred_locale"]` = "en-GB" |
| 50 | - - Visits `/en/dashboard` |
| 51 | - - System uses "en-GB" for translations |
| 52 | - - URLs remain `/en/` (not `/en-GB/`) |
| 48 | + Dialect resolution is purely URL-driven: a base code maps to its |
| 49 | + default dialect and nothing else. A logged-in user's locale preference |
| 50 | + is intentionally not consulted (see `PhoenixKitWeb.Users.Auth` for the |
| 51 | + URL-is-authoritative rationale), so `/en/dashboard` always resolves to |
| 52 | + the default `en-US` regardless of who is signed in. |
| 53 53 | |
| 54 54 | ## Examples |
| 55 55 | |
| @@ -67,12 +67,8 @@ defmodule PhoenixKit.Modules.Languages.DialectMapper do | |
| 67 67 | iex> DialectMapper.base_to_dialect("pt") |
| 68 68 | "pt-BR" |
| 69 69 | |
| 70 | - # Resolve dialect with user preference (stored in custom_fields) |
| 71 | - iex> user = %User{custom_fields: %{"preferred_locale" => "en-GB"}} |
| 72 | - iex> DialectMapper.resolve_dialect("en", user) |
| 73 | - "en-GB" |
| 74 | - |
| 75 | - iex> DialectMapper.resolve_dialect("en", nil) |
| 70 | + # Resolve a base code to its default dialect (URL-driven) |
| 71 | + iex> DialectMapper.resolve_dialect("en") |
| 76 72 | "en-US" |
| 77 73 | |
| 78 74 | ## Validation |
| @@ -249,53 +245,28 @@ defmodule PhoenixKit.Modules.Languages.DialectMapper do | |
| 249 245 | end |
| 250 246 | |
| 251 247 | @doc """ |
| 252 | - Resolves the full dialect code for a user visiting a base language URL. |
| 248 | + Resolves the full dialect code for a base language URL. |
| 253 249 | |
| 254 | - Resolution priority: |
| 255 | - 1. User's saved preference (if authenticated and preference matches base code) |
| 256 | - 2. Default dialect mapping for that base language |
| 250 | + The URL is authoritative: the base code maps straight to its default |
| 251 | + dialect via `base_to_dialect/1`. User locale preferences are |
| 252 | + deliberately NOT consulted here — locale resolution is URL-driven |
| 253 | + across both the LiveView mount and the HTTP plug (see the rationale in |
| 254 | + `PhoenixKitWeb.Users.Auth`). Resolving without a user keeps a logged-in |
| 255 | + user's `custom_fields["preferred_locale"]` from silently upgrading e.g. |
| 256 | + base `"en"` to `"en-GB"`. |
| 257 257 | |
| 258 258 | ## Examples |
| 259 259 | |
| 260 | - iex> user = %User{custom_fields: %{"preferred_locale" => "en-GB"}} |
| 261 | - iex> DialectMapper.resolve_dialect("en", user) |
| 262 | - "en-GB" |
| 263 | - |
| 264 | - iex> user = %User{custom_fields: %{"preferred_locale" => "es-MX"}} |
| 265 | - iex> DialectMapper.resolve_dialect("en", user) |
| 266 | - "en-US" # Preference doesn't match base, use default |
| 267 | - |
| 268 | - iex> DialectMapper.resolve_dialect("en", nil) |
| 260 | + iex> DialectMapper.resolve_dialect("en") |
| 269 261 | "en-US" |
| 270 262 | |
| 271 | - iex> guest = %{some_field: "value"} |
| 272 | - iex> DialectMapper.resolve_dialect("es", guest) |
| 263 | + iex> DialectMapper.resolve_dialect("es") |
| 273 264 | "es-ES" |
| 274 265 | |
| 275 | - ## Security |
| 276 | - |
| 277 | - User preference only applied if it matches the requested base code. |
| 278 | - This prevents users from forcing unintended locales via preference tampering. |
| 279 | - |
| 280 | - ## Graceful Degradation |
| 281 | - |
| 282 | - If user preference becomes invalid (dialect disabled, typo, etc.), |
| 283 | - system falls back to default mapping. No crashes or errors. |
| 266 | + iex> DialectMapper.resolve_dialect("ja") |
| 267 | + "ja" |
| 284 268 | """ |
| 285 | - def resolve_dialect(base_code, user \\ nil) |
| 286 | - |
| 287 | - def resolve_dialect(base_code, %{custom_fields: %{"preferred_locale" => preferred}} = _user) |
| 288 | - when is_binary(preferred) do |
| 289 | - # Verify user's preference matches the base code in URL |
| 290 | - # Security: prevents locale preference injection attacks |
| 291 | - if extract_base(preferred) == String.downcase(base_code) do |
| 292 | - preferred |
| 293 | - else |
| 294 | - base_to_dialect(base_code) |
| 295 | - end |
| 296 | - end |
| 297 | - |
| 298 | - def resolve_dialect(base_code, _user) do |
| 269 | + def resolve_dialect(base_code) when is_binary(base_code) do |
| 299 270 | base_to_dialect(base_code) |
| 300 271 | end |
| @@ -354,55 +354,6 @@ defmodule PhoenixKit.Users.Auth.User do | |
| 354 354 | change(user, confirmed_at: nil) |
| 355 355 | end |
| 356 356 | |
| 357 | - @doc """ |
| 358 | - A user changeset for updating preferred locale/dialect. |
| 359 | - |
| 360 | - This allows authenticated users to select their preferred dialect variant |
| 361 | - (e.g., en-GB instead of en-US) while URLs continue to show base codes. |
| 362 | - The locale is stored in the `custom_fields` JSONB column. |
| 363 | - |
| 364 | - ## Validation |
| 365 | - |
| 366 | - - Format: Must match ~r/^[a-z]{2,3}(-[A-Za-z]{2,4})?$/ |
| 367 | - - Existence: Must exist in predefined language list |
| 368 | - - NULL/empty allowed: Indicates "use system default" |
| 369 | - |
| 370 | - ## Examples |
| 371 | - |
| 372 | - iex> preferred_locale_changeset(user, %{preferred_locale: "en-GB"}) |
| 373 | - #Ecto.Changeset<...> |
| 374 | - |
| 375 | - iex> preferred_locale_changeset(user, %{preferred_locale: nil}) |
| 376 | - #Ecto.Changeset<...> # Clears preference, uses defaults |
| 377 | - |
| 378 | - iex> preferred_locale_changeset(user, %{preferred_locale: "invalid"}) |
| 379 | - #Ecto.Changeset<errors: [preferred_locale: {"must be a valid locale format", []}]> |
| 380 | - """ |
| 381 | - def preferred_locale_changeset(user, attrs) do |
| 382 | - locale = Map.get(attrs, :preferred_locale) || Map.get(attrs, "preferred_locale") |
| 383 | - |
| 384 | - case validate_locale_value(locale) do |
| 385 | - :ok -> |
| 386 | - # Merge locale into custom_fields |
| 387 | - current_fields = user.custom_fields || %{} |
| 388 | - |
| 389 | - updated_fields = |
| 390 | - if locale && locale != "" do |
| 391 | - Map.put(current_fields, "preferred_locale", locale) |
| 392 | - else |
| 393 | - Map.delete(current_fields, "preferred_locale") |
| 394 | - end |
| 395 | - |
| 396 | - user |
| 397 | - |> change(custom_fields: updated_fields) |
| 398 | - |
| 399 | - {:error, message} -> |
| 400 | - user |
| 401 | - |> change() |
| 402 | - |> add_error(:preferred_locale, message) |
| 403 | - end |
| 404 | - end |
| 405 | - |
| 406 357 | @doc """ |
| 407 358 | Validates a locale value for format and existence. |
| 408 359 | |
| @@ -434,25 +385,6 @@ defmodule PhoenixKit.Users.Auth.User do | |
| 434 385 | |
| 435 386 | def validate_locale_value(_), do: {:error, "must be a string"} |
| 436 387 | |
| 437 | - @doc """ |
| 438 | - Gets the user's preferred locale from custom_fields. |
| 439 | - |
| 440 | - Returns nil if not set (indicating system default should be used). |
| 441 | - |
| 442 | - ## Examples |
| 443 | - |
| 444 | - iex> get_preferred_locale(%User{custom_fields: %{"preferred_locale" => "en-GB"}}) |
| 445 | - "en-GB" |
| 446 | - |
| 447 | - iex> get_preferred_locale(%User{custom_fields: %{}}) |
| 448 | - nil |
| 449 | - """ |
| 450 | - def get_preferred_locale(%__MODULE__{custom_fields: fields}) when is_map(fields) do |
| 451 | - Map.get(fields, "preferred_locale") |
| 452 | - end |
| 453 | - |
| 454 | - def get_preferred_locale(%__MODULE__{}), do: nil |
| 455 | - |
| 456 388 | @doc """ |
| 457 389 | Verifies the password. |
Loading more files…