Packages
localize
0.11.0
1.0.0-rc.6
1.0.0-rc.5
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.41.3
0.41.2
0.41.1
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.1
0.30.0
retired
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
0.1.0-alpha.1
Localization (parsing, formatting) of numbers, dates/time/calendar, units of measure, messages and lists. Includes localized collation.
Current section
Files
Jump to
Current section
Files
lib/localize/locale/provider/cache.ex
defmodule Localize.Locale.Provider.Cache do
@moduledoc """
On-disk cache for downloaded locale data files.
This module is responsible for reading and writing locale ETF
files under the directory returned by
`Localize.Locale.Provider.locale_cache_dir/0`, and for detecting
when a cached file is stale relative to `Localize.version/0`.
## Primary API
* `get/1` — returns cached locale data, or an error tuple if the
file is missing or stale.
* `store/2` — writes locale data to the cache directory.
* `stale?/1` — checks whether a cached locale file is stale.
* `path/1` — returns the absolute path for a locale's cache file.
"""
alias Localize.Locale.Provider
@typedoc "A locale identifier as an atom."
@type locale_id :: atom()
@doc """
Returns the absolute cache file path for the given locale.
### Arguments
* `locale_id` is a locale identifier atom.
### Returns
* A string path to the locale's cache file.
### Examples
iex> path = Localize.Locale.Provider.Cache.path(:en)
iex> String.ends_with?(path, "en.etf")
true
"""
@spec path(locale_id()) :: String.t()
def path(locale_id) when is_atom(locale_id) do
Path.join(Provider.locale_cache_dir(), Provider.locale_file_name(locale_id))
end
@doc """
Retrieves locale data from the cache directory.
Reads the locale's cache file, decodes it, and validates that
its `:version` field matches `Localize.version/0`.
### Arguments
* `locale_id` is a locale identifier atom.
### Returns
* `{:ok, locale_data}` if the cache file is present and its
version matches the current version.
* `{:error, Localize.LocaleIsStaleError.t()}` if the cache file
is present but its version does not match.
* `{:error, Localize.LocaleNotFoundInCacheError.t()}` if the
cache file is not present.
"""
@spec get(locale_id()) ::
{:ok, map()} | {:error, Exception.t()}
def get(locale_id) when is_atom(locale_id) do
cache_dir = Provider.locale_cache_dir()
bundled_dir = Provider.default_locale_cache_dir()
file_name = Provider.locale_file_name(locale_id)
search_paths =
if cache_dir == bundled_dir do
[Path.join(cache_dir, file_name)]
else
[Path.join(cache_dir, file_name), Path.join(bundled_dir, file_name)]
end
read_first(locale_id, search_paths)
end
defp read_first(locale_id, [file_path]) do
read_and_validate(locale_id, file_path)
end
defp read_first(locale_id, [file_path | rest]) do
case read_and_validate(locale_id, file_path) do
{:ok, _locale_data} = success -> success
{:error, _} -> read_first(locale_id, rest)
end
end
defp read_and_validate(locale_id, file_path) do
case File.read(file_path) do
{:ok, binary} ->
locale_data = :erlang.binary_to_term(binary)
current_version = Localize.version()
cached_version = Map.get(locale_data, :version)
if versions_match?(cached_version, current_version) do
{:ok, locale_data}
else
{:error,
Localize.LocaleIsStaleError.exception(
locale_id: locale_id,
cached_version: cached_version,
current_version: current_version
)}
end
{:error, :enoent} ->
{:error,
Localize.LocaleNotFoundInCacheError.exception(
locale_id: locale_id,
path: file_path
)}
{:error, reason} ->
{:error,
Localize.LocaleNotFoundInCacheError.exception(
locale_id: locale_id,
path: "#{file_path} (#{inspect(reason)})"
)}
end
end
@doc """
Stores locale content in the cache directory.
Creates the cache directory if it does not already exist and
writes `content` to the locale's cache file. `content` should
be the raw binary form of the locale ETF file.
### Arguments
* `locale_id` is a locale identifier atom.
* `content` is a binary containing the encoded locale data.
### Returns
* `{:ok, path}` where `path` is the absolute path the content
was written to.
* `{:error, Localize.LocaleCacheWriteError.t()}` on failure.
"""
@spec store(locale_id(), binary()) ::
{:ok, String.t()} | {:error, Exception.t()}
def store(locale_id, content) when is_atom(locale_id) and is_binary(content) do
file_path = path(locale_id)
dir = Path.dirname(file_path)
with :ok <- File.mkdir_p(dir),
:ok <- File.write(file_path, content) do
{:ok, file_path}
else
{:error, reason} ->
{:error,
Localize.LocaleCacheWriteError.exception(
locale_id: locale_id,
path: file_path,
reason: inspect(reason)
)}
end
end
@doc """
Returns whether a cached locale file is stale.
A cache file is considered stale if its `:version` field does
not equal `Localize.version/0`. A file that is missing or
unreadable is also considered stale.
### Arguments
* `locale_id` is a locale identifier atom.
### Returns
* `true` if the cache file is missing, unreadable, or its
version does not match `Localize.version/0`.
* `false` if the cache file is present and its version matches.
"""
@spec stale?(locale_id()) :: boolean()
def stale?(locale_id) when is_atom(locale_id) do
file_path = path(locale_id)
case File.read(file_path) do
{:ok, binary} ->
locale_data = :erlang.binary_to_term(binary)
not versions_match?(Map.get(locale_data, :version), Localize.version())
{:error, _reason} ->
true
end
end
defp versions_match?(%Version{} = cached, %Version{} = current) do
Version.compare(cached, current) == :eq
end
defp versions_match?(_cached, _current) do
false
end
end