Skip to content

Commit acde44e

Browse files
committed
Release 1.0.0: add credo and strict dialyzer, fix HTML select and options specs
1 parent a7663e1 commit acde44e

14 files changed

Lines changed: 109 additions & 78 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@
22

33
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5-
## [1.0.0-rc.0] — 2026-07-30
5+
## [1.0.0] — 2026-07-31
6+
7+
The first stable release, built on Localize 1.0.
68

79
### Changes
810

9-
* Updated `Localize` to `"~> 1.0-rc"`.
11+
* Requires `localize ~> 1.0`.
12+
13+
### Fixed
14+
15+
* `Localize.HTML.Unit.select/3` is specified as returning `{:error, Exception.t()}`. Localize 1.0 reports an invalid unit or locale as an exception struct rather than a `{module, message}` tuple, so the previous specification did not describe what the function returns.
16+
17+
* `currency_options/1`, `locale_options/1`, `territory_options/1` and `unit_options/1` no longer claim to return an error tuple. They validate by raising, so the documented error return could not occur.
1018

1119
## [0.8.0] — 2026-06-25
1220

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Add `localize_web` to your list of dependencies in `mix.exs`:
2727
```elixir
2828
def deps do
2929
[
30-
{:localize_web, "~> 0.1.0"}
30+
{:localize_web, "~> 1.0"}
3131
]
3232
end
3333
```

lib/localize/html/currency.ex

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ defmodule Localize.HTML.Currency do
4545
4646
* A `t:Phoenix.HTML.safe/0` select tag, or
4747
48-
* `{:error, {module(), binary()}}` if validation fails.
49-
5048
### Examples
5149
5250
iex> Localize.HTML.Currency.select(:my_form, :currency, selected: :USD)
@@ -79,12 +77,10 @@ defmodule Localize.HTML.Currency do
7977
8078
### Returns
8179
82-
* A list of `{display_name, currency_code}` tuples, or
83-
84-
* `{:error, {module(), binary()}}` if validation fails.
80+
* A list of `{display_name, currency_code}` tuples.
8581
8682
"""
87-
@spec currency_options(select_options) :: list(tuple()) | {:error, {module(), binary()}}
83+
@spec currency_options(select_options) :: list(tuple())
8884

8985
def currency_options(options \\ [])
9086

lib/localize/html/locale.ex

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ defmodule Localize.HTML.Locale do
5656
5757
* A `t:Phoenix.HTML.safe/0` select tag, or
5858
59-
* `{:error, {module(), binary()}}` if validation fails.
60-
6159
### Examples
6260
6361
iex> Localize.HTML.Locale.select(:my_form, :locale_list, selected: "en")
@@ -89,12 +87,10 @@ defmodule Localize.HTML.Locale do
8987
9088
### Returns
9189
92-
* A list of `{display_name, locale_string}` tuples, or
93-
94-
* `{:error, {module(), binary()}}` if validation fails.
90+
* A list of `{display_name, locale_string}` tuples.
9591
9692
"""
97-
@spec locale_options(select_options) :: list(tuple()) | {:error, {module(), binary()}}
93+
@spec locale_options(select_options) :: list(tuple())
9894

9995
def locale_options(options \\ [])
10096

lib/localize/html/territory.ex

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ defmodule Localize.HTML.Territory do
5858
5959
* A `t:Phoenix.HTML.safe/0` select tag, or
6060
61-
* `{:error, {module(), binary()}}` if validation fails.
62-
6361
### Examples
6462
6563
iex> Localize.HTML.Territory.select(:my_form, :territory, selected: :AU)
@@ -92,12 +90,10 @@ defmodule Localize.HTML.Territory do
9290
9391
### Returns
9492
95-
* A list of `{display_name, territory_code}` tuples, or
96-
97-
* `{:error, {module(), binary()}}` if validation fails.
93+
* A list of `{display_name, territory_code}` tuples.
9894
9995
"""
100-
@spec territory_options(select_options) :: list(tuple()) | {:error, {module(), binary()}}
96+
@spec territory_options(select_options) :: list(tuple())
10197

10298
def territory_options(options \\ [])
10399

@@ -221,16 +217,21 @@ defmodule Localize.HTML.Territory do
221217
end
222218

223219
defp name_from_territory(territory, options) do
224-
with {:ok, name} <- Localize.Territory.display_name(territory, options) do
225-
name
226-
else
227-
{:error, _} ->
228-
default_options = Keyword.delete(options, :style)
220+
case Localize.Territory.display_name(territory, options) do
221+
{:ok, name} -> name
222+
{:error, _reason} -> name_from_default_style(territory, options)
223+
end
224+
end
225+
226+
# Not every territory has a name in every style, so a failed lookup
227+
# retries with the locale's default style before falling back to the
228+
# territory code itself.
229+
defp name_from_default_style(territory, options) do
230+
default_options = Keyword.delete(options, :style)
229231

230-
case Localize.Territory.display_name(territory, default_options) do
231-
{:ok, name} -> name
232-
_ -> to_string(territory)
233-
end
232+
case Localize.Territory.display_name(territory, default_options) do
233+
{:ok, name} -> name
234+
_error -> to_string(territory)
234235
end
235236
end
236237

lib/localize/html/unit.ex

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ defmodule Localize.HTML.Unit do
4848
4949
* A `t:Phoenix.HTML.safe/0` select tag, or
5050
51-
* `{:error, {module(), binary()}}` if validation fails.
52-
5351
### Examples
5452
5553
iex> Localize.HTML.Unit.select(:my_form, :unit, selected: :foot)
@@ -61,7 +59,7 @@ defmodule Localize.HTML.Unit do
6159
select_options
6260
) ::
6361
Phoenix.HTML.safe()
64-
| {:error, {module(), binary()}}
62+
| {:error, Exception.t()}
6563

6664
def select(form, field, options \\ [])
6765

@@ -82,12 +80,10 @@ defmodule Localize.HTML.Unit do
8280
8381
### Returns
8482
85-
* A list of `{display_name, unit_code}` tuples, or
86-
87-
* `{:error, {module(), binary()}}` if validation fails.
83+
* A list of `{display_name, unit_code}` tuples.
8884
8985
"""
90-
@spec unit_options(select_options) :: list(tuple()) | {:error, {module(), binary()}}
86+
@spec unit_options(select_options) :: list(tuple())
9187

9288
def unit_options(options \\ [])
9389

lib/localize/plug.ex

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,7 @@ defmodule Localize.Plug do
5454

5555
with {:ok, locale} <- Localize.validate_locale(locale) do
5656
Localize.put_locale(locale)
57-
58-
Enum.each(gettext_backends, fn gettext_backend ->
59-
case Localize.Locale.gettext_locale_id(locale, gettext_backend) do
60-
{:ok, gettext_locale} ->
61-
Gettext.put_locale(gettext_backend, gettext_locale)
62-
63-
{:error, _reason} ->
64-
require Logger
65-
66-
Logger.warning(
67-
"Localize.Plug.put_locale_from_session/2: locale #{inspect(locale.cldr_locale_id)} " <>
68-
"does not have a matching Gettext locale for backend #{inspect(gettext_backend)}. " <>
69-
"No Gettext locale has been set."
70-
)
71-
end
72-
end)
57+
Enum.each(gettext_backends, &put_gettext_locale(&1, locale))
7358

7459
{:ok, locale}
7560
end
@@ -79,6 +64,25 @@ defmodule Localize.Plug do
7964
{:error, {Localize.UnknownLocaleError, "No locale was found in the session"}}
8065
end
8166

67+
# A locale that Gettext has no translations for is a configuration
68+
# mismatch rather than an error: the locale is still set for Localize,
69+
# and only the Gettext locale is left alone.
70+
defp put_gettext_locale(gettext_backend, locale) do
71+
case Localize.Locale.gettext_locale_id(locale, gettext_backend) do
72+
{:ok, gettext_locale} ->
73+
Gettext.put_locale(gettext_backend, gettext_locale)
74+
75+
{:error, _reason} ->
76+
require Logger
77+
78+
Logger.warning(
79+
"Localize.Plug.put_locale_from_session/2: locale #{inspect(locale.cldr_locale_id)} " <>
80+
"does not have a matching Gettext locale for backend #{inspect(gettext_backend)}. " <>
81+
"No Gettext locale has been set."
82+
)
83+
end
84+
end
85+
8286
defp normalize_gettext_backends(nil), do: []
8387
defp normalize_gettext_backends(backend) when is_atom(backend), do: [backend]
8488
defp normalize_gettext_backends(backends) when is_list(backends), do: backends

lib/localize/plug/put_locale.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ defmodule Localize.Plug.PutLocale do
8989

9090
@doc false
9191
def call(conn, options) do
92-
if locale = locale_from_params(conn, options[:from], options) || default(conn, options) do
92+
locale = locale_from_params(conn, options[:from], options) || default(conn, options)
93+
94+
if locale do
9395
Localize.put_locale(locale)
9496

9597
Enum.each(options[:gettext], fn gettext_backend ->

lib/localize/plug/put_session.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ defmodule Localize.Plug.PutSession do
4444
"Invalid option for `:as`. Valid settings are :string or :language_tag. Found #{inspect(other)}"
4545
end
4646

47-
if length(options) > 0,
47+
if options != [],
4848
do:
4949
raise(ArgumentError, "Invalid options. Valid option is `:as`. Found #{inspect(options)}")
5050

lib/localize/routes.ex

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,15 @@ defmodule Localize.Routes do
102102
Localize.Routes.delete_original_path(routes)
103103
)
104104

105+
# `Phoenix.Router.Route.exprs/1` gained a second argument in later
106+
# Phoenix releases. `apply/3` is what lets one build support both
107+
# arities — a direct call to the absent one would not compile.
105108
routes_with_exprs =
106109
if function_exported?(Phoenix.Router.Route, :exprs, 2) do
110+
# credo:disable-for-next-line Credo.Check.Refactor.Apply
107111
Enum.map(routes, &{&1, apply(Phoenix.Router.Route, :exprs, [&1, forwards])})
108112
else
113+
# credo:disable-for-next-line Credo.Check.Refactor.Apply
109114
Enum.map(routes, &{&1, apply(Phoenix.Router.Route, :exprs, [&1])})
110115
end
111116

@@ -202,24 +207,8 @@ defmodule Localize.Routes do
202207
defmacro localize(locale_ids, do: route) when is_list(locale_ids) do
203208
gettext_backend = Module.get_attribute(__CALLER__.module, :_gettext_backend)
204209

205-
for locale_id <- locale_ids do
206-
with {:ok, locale} <- Localize.validate_locale(locale_id) do
207-
case Localize.Locale.gettext_locale_id(locale, gettext_backend) do
208-
{:ok, gettext_locale} ->
209-
quote do
210-
localize(
211-
{unquote(Macro.escape(locale)), unquote(gettext_locale)},
212-
unquote(route)
213-
)
214-
end
215-
216-
{:error, _reason} ->
217-
warn_no_gettext_locale(locale_id, route)
218-
end
219-
else
220-
{:error, %{__exception__: true} = exception} -> raise exception
221-
end
222-
end
210+
locale_ids
211+
|> Enum.map(&localized_route(&1, route, gettext_backend))
223212
|> Enum.reject(&is_nil/1)
224213
|> Enum.uniq_by(&canonical_route/1)
225214
end
@@ -281,6 +270,32 @@ defmodule Localize.Routes do
281270
"""
282271
end
283272

273+
# Expands one locale into a `localize/2` call carrying both the
274+
# resolved language tag and its Gettext locale. A locale with no
275+
# Gettext translations is warned about and skipped; an invalid locale
276+
# identifier is a mistake in the router and raises.
277+
defp localized_route(locale_id, route, gettext_backend) do
278+
case Localize.validate_locale(locale_id) do
279+
{:ok, locale} -> gettext_localized_route(locale, locale_id, route, gettext_backend)
280+
{:error, exception} -> raise exception
281+
end
282+
end
283+
284+
defp gettext_localized_route(locale, locale_id, route, gettext_backend) do
285+
case Localize.Locale.gettext_locale_id(locale, gettext_backend) do
286+
{:ok, gettext_locale} ->
287+
quote do
288+
localize(
289+
{unquote(Macro.escape(locale)), unquote(gettext_locale)},
290+
unquote(route)
291+
)
292+
end
293+
294+
{:error, _reason} ->
295+
warn_no_gettext_locale(locale_id, route)
296+
end
297+
end
298+
284299
defp do_localize(field, {locale, gettext_locale}, gettext_backend, {verb, meta, [path | args]}) do
285300
locale = eval_locale(locale)
286301

0 commit comments

Comments
 (0)