Skip to content

Commit 13c2551

Browse files
committed
Locale loader walks the parent chain and finally selected :en
1 parent f04d5b0 commit 13c2551

2 files changed

Lines changed: 92 additions & 2 deletions

File tree

lib/localize/locale/loader.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ defmodule Localize.Locale.Loader do
109109
:ok
110110
else
111111
try do
112-
case provider.load(locale) do
113-
{:ok, locale_data} ->
112+
case Localize.Locale.Provider.load_with_fallback(provider, locale) do
113+
{:ok, locale_data, _resolved_locale_id} ->
114114
provider.store(locale, locale_data)
115115

116116
{:error, _reason} = error ->

lib/localize/locale/provider.ex

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,96 @@ defmodule Localize.Locale.Provider do
171171
Application.get_env(:localize, :locale_provider, Localize.Locale.Provider.PersistentTerm)
172172
end
173173

174+
@doc """
175+
Loads locale data with fallback through the CLDR parent chain.
176+
177+
Attempts to load the requested locale via `provider.load/1`. If the
178+
load fails, walks up the CLDR locale inheritance chain (e.g.
179+
`en-AU` → `en` → `und`) trying each parent in turn. If the entire
180+
chain is exhausted without success, falls back to `:en` which is
181+
guaranteed to be present.
182+
183+
Returns `{:ok, locale_data, resolved_locale_id}` so the caller
184+
knows which locale was actually loaded.
185+
186+
### Arguments
187+
188+
* `provider` is the module implementing `Localize.Locale.Provider`.
189+
190+
* `locale` is a locale identifier atom or a `t:Localize.LanguageTag.t/0`.
191+
192+
### Returns
193+
194+
* `{:ok, locale_data, resolved_locale_id}` on success.
195+
196+
* `{:error, exception}` if even the `:en` fallback fails (should not
197+
happen in normal operation).
198+
199+
"""
200+
@spec load_with_fallback(module(), locale()) ::
201+
{:ok, map(), locale_id()} | {:error, Exception.t()}
202+
def load_with_fallback(provider, locale) do
203+
locale_id = Localize.Locale.to_locale_id(locale)
204+
205+
case provider.load(locale) do
206+
{:ok, locale_data} ->
207+
{:ok, locale_data, locale_id}
208+
209+
{:error, _exception} ->
210+
require Logger
211+
212+
Logger.debug(
213+
"Locale #{inspect(locale_id)} not available, walking parent chain",
214+
domain: [:localize]
215+
)
216+
217+
walk_parent_chain(provider, locale_id)
218+
end
219+
end
220+
221+
defp walk_parent_chain(provider, locale_id) do
222+
require Logger
223+
224+
case Localize.Locale.parent(to_string(locale_id)) do
225+
{:ok, parent_tag} ->
226+
parent_id = Localize.Locale.to_locale_id(parent_tag)
227+
228+
Logger.debug(
229+
"Trying parent locale #{inspect(parent_id)} for #{inspect(locale_id)}",
230+
domain: [:localize]
231+
)
232+
233+
case provider.load(parent_id) do
234+
{:ok, locale_data} ->
235+
{:ok, locale_data, parent_id}
236+
237+
{:error, _} ->
238+
walk_parent_chain(provider, parent_id)
239+
end
240+
241+
{:error, _} ->
242+
# Reached root (und) with no success — fall back to :en
243+
fallback_to_en(provider, locale_id)
244+
end
245+
end
246+
247+
defp fallback_to_en(provider, original_locale_id) do
248+
require Logger
249+
250+
Logger.debug(
251+
"Parent chain exhausted for #{inspect(original_locale_id)}, falling back to :en",
252+
domain: [:localize]
253+
)
254+
255+
case provider.load(:en) do
256+
{:ok, locale_data} ->
257+
{:ok, locale_data, :en}
258+
259+
{:error, _} = error ->
260+
error
261+
end
262+
end
263+
174264
@doc """
175265
Returns the directory in which downloaded locale data is cached.
176266

0 commit comments

Comments
 (0)