/
loader.ex
234 lines (191 loc) · 6.91 KB
/
loader.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
defmodule Cldr.Locale.Loader do
@moduledoc """
Provides a public interface to read the
raw JSON locale files and return the
CLDR data in a consistent format.
The functions in this module are intended for the
use of authors writing additional CLDR-based
libraries.
In addition, the functions in this module are
intended for use at compile-time - not runtime -
since reading, decoding and processing a
locale file is an expensive operation.
"""
alias Cldr.Config
alias Cldr.Locale
@max_concurrency System.schedulers_online() * 2
@timeout 10_000
@doc """
Returns a list of all locales that are configured and available
in the CLDR repository.
## Examples
iex> Cldr.Locale.Loader.known_locale_names %Cldr.Config{locales: ["en", "de"]}
[:de, :en, :und]
"""
@spec known_locale_names(Config.t() | Cldr.backend()) :: [Locale.locale_name()]
def known_locale_names(backend) when is_atom(backend) do
backend.__cldr__(:config)
|> known_locale_names
end
def known_locale_names(%Config{} = config) do
Cldr.Config.configured_locale_names(config)
end
@doc """
Returns a list of all locales that have RBNF data and that are
configured and available in the CLDR repository.
"""
@spec known_rbnf_locale_names(Config.t()) :: [Locale.locale_name()]
def known_rbnf_locale_names(%Cldr.Config{locales: :all} = config) do
config
|> known_locale_names()
|> Task.async_stream(
fn locale_name ->
rbnf =
locale_name
|> get_locale(config)
|> Map.get(:rbnf)
if Enum.empty?(rbnf), do: nil, else: locale_name
end,
max_concurrency: @max_concurrency,
timeout: @timeout
)
|> Enum.reduce_while([], fn
{:ok, nil}, acc -> {:cont, acc}
{:ok, locale_name}, acc -> {:cont, [locale_name | acc]}
end)
|> Enum.sort()
end
def known_rbnf_locale_names(config) do
known_locale_names(config)
|> Enum.filter(fn locale -> Map.get(get_locale(locale, config), :rbnf) != %{} end)
end
@doc """
Read the locale json, decode it and make any necessary transformations.
This is the only place that we read the locale and we only
read it once. All other uses of locale data are references
to this data.
Additionally the intention is that this is read only at compile time
and used to construct accessor functions in other modules so that
during production run there is no file access or decoding.
"""
@spec get_locale(Locale.locale_name(), config_or_backend :: Config.t() | Cldr.backend()) ::
map() | no_return()
def get_locale(locale, %{data_dir: _} = config) when is_atom(locale) do
do_get_locale(locale, config)
end
def get_locale(locale, backend) when is_atom(locale) and is_atom(backend) do
do_get_locale(locale, backend.__cldr__(:config))
end
@doc false
def do_get_locale(locale, config) when is_atom(locale) do
{:ok, path} =
case Config.locale_path(locale, config) do
{:ok, path} ->
{:ok, path}
{:error, :not_found} ->
raise RuntimeError, message: "Locale definition was not found for #{inspect(locale)}"
end
do_get_locale(locale, path, Cldr.Locale.Cache.compiling?())
end
@alt_keys ["default", "menu", "short", "long", "variant", "standard"]
@lenient_parse_keys ["date", "general", "number"]
@language_keys ["language", "language_variants"]
@remaining_modules Cldr.Config.required_modules() --
[
"locale_display_names",
"languages",
"lenient_parse",
"dates",
]
@doc false
def do_get_locale(locale, path, false) do
path
|> read_locale_file!
|> Config.json_library().decode!
|> assert_valid_keys!(locale)
|> Cldr.Map.integerize_keys(filter: "list_formats")
|> Cldr.Map.integerize_keys(filter: "number_formats")
|> Cldr.Map.atomize_values(filter: "number_systems")
|> Cldr.Map.atomize_keys(filter: "locale_display_names", skip: @language_keys)
|> Cldr.Map.atomize_keys(filter: :language, only: @alt_keys)
|> Cldr.Map.atomize_keys(filter: "languages", only: @alt_keys)
|> Cldr.Map.atomize_keys(filter: "lenient_parse", only: @lenient_parse_keys)
|> Cldr.Map.atomize_keys(filter: @remaining_modules)
|> Cldr.Map.atomize_values(filter: :layout)
|> structure_date_formats()
|> Cldr.Map.atomize_keys(level: 1..1)
|> parse_version()
|> Map.put(:name, locale)
end
@doc false
def do_get_locale(locale, path, true) when is_atom(locale) do
Cldr.Locale.Cache.get_locale(locale, path)
end
defp parse_version(content) do
case Map.get(content, :version) do
nil -> content
version -> Map.put(content, :version, Version.parse!(version))
end
end
# Read the file.
# TODO remove when :all is deprecated in Elixir 1.17
@read_flag if Version.compare(System.version(), "1.13.0-dev") == :lt, do: :all, else: :eof
@doc false
def read_locale_file!(path) do
Cldr.maybe_log("Cldr.Config reading locale file #{inspect(path)}")
{:ok, contents} = File.open(path, [:read, :binary, :utf8], &IO.read(&1, @read_flag))
contents
end
@date_atoms [
"exemplar_city",
"long",
"standard",
"generic",
"short",
"daylight",
"formal",
"daylight_savings",
"generic"
]
defp structure_date_formats(content) do
dates =
content
|> Map.get("dates")
|> Cldr.Map.integerize_keys(only: Cldr.Config.keys_to_integerize())
|> Cldr.Map.deep_map(fn
{"number_system", value} ->
{:number_system,
Cldr.Map.atomize_values(value) |> Cldr.Map.stringify_keys(except: :all)}
other ->
other
end)
|> Cldr.Map.atomize_keys(only: @date_atoms)
|> Cldr.Map.atomize_keys(filter: "calendars", skip: :number_system)
|> Cldr.Map.atomize_keys(filter: "time_zone_names", level: 1..2)
|> Cldr.Map.atomize_keys(level: 1..1)
Map.put(content, :dates, dates)
end
@doc false
def underscore(string) when is_binary(string) do
string
|> Cldr.String.to_underscore()
end
def underscore(other), do: other
# Simple check that the locale content contains what we expect
# by checking it has the keys we used when the locale was consolidated.
# Set the environment variable DEV to bypass this check. That is
# only required if adding new content modules to a locale - which is
# an uncommon activity.
# We don't validate the "version" key because its not in older
# locale files which we will upgrade latter in the resolution cycle.
defp assert_valid_keys!(content, locale) do
for module <- Config.required_modules() do
if !Map.has_key?(content, module) && !Elixir.System.get_env("DEV") do
raise RuntimeError,
message:
"Locale file #{inspect(locale)} is invalid - locale map key #{inspect(module)} was not found."
end
end
content
end
end