Packages
gettext
0.6.0
1.0.2
1.0.1
retired
1.0.0
0.26.2
0.26.1
0.26.0
retired
0.25.0
0.24.0
0.23.1
0.23.0
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.0
Internationalization and localization through gettext
Current section
Files
Jump to
Current section
Files
lib/gettext/po/parser.ex
defmodule Gettext.PO.Parser do
@moduledoc false
alias Gettext.PO.Translation
alias Gettext.PO.PluralTranslation
@doc """
Parses a list of tokens into a list of translations.
"""
@spec parse([Gettext.PO.Tokenizer.token]) ::
{:ok, [binary], [Gettext.PO.translation]} | Gettext.PO.parse_error
def parse(tokens) do
case :gettext_po_parser.parse(tokens) do
{:ok, translations} ->
do_parse(translations)
{:error, _reason} = error ->
parse_error(error)
end
end
defp do_parse(translations) do
translations = Enum.map(translations, &to_struct/1)
case check_for_duplicates(translations) do
{:error, _line, _reason} = error ->
error
:ok ->
{headers, translations} = extract_headers(translations)
{:ok, headers, translations}
end
end
defp to_struct({:translation, translation}),
do: struct(Translation, translation) |> extract_references() |> extract_flags()
defp to_struct({:plural_translation, translation}),
do: struct(PluralTranslation, translation) |> extract_references() |> extract_flags()
defp parse_error({:error, {line, _module, reason}}) do
{:error, line, IO.chardata_to_string(reason)}
end
defp extract_references(%{__struct__: _, comments: comments} = translation) do
{reference_comments, other_comments} = Enum.partition(comments, &match?("#:" <> _, &1))
references =
for "#:" <> contents <- reference_comments,
(contents = String.strip(contents)) != "",
do: parse_reference(contents)
%{translation | references: references, comments: other_comments}
end
defp parse_reference(ref) do
[file, line] = String.split(ref, ":")
{file, String.to_integer(line)}
end
defp extract_flags(%{__struct__: _, comments: comments} = translation) do
{flag_comments, other_comments} = Enum.partition(comments, &match?("#," <> _, &1))
%{translation | flags: parse_flags(flag_comments), comments: other_comments}
end
defp parse_flags(flag_comments) do
flag_comments
|> Stream.map(fn("#," <> content) -> content end)
|> Stream.flat_map(&String.split(&1, ~r/\s+/, trim: true))
|> Enum.into(MapSet.new)
end
# If the first translation has an empty msgid, it's assumed to represent
# headers. Headers will be in the msgstr of this "fake" translation, one on
# each line. For now, we'll just separate those lines in order to get a list
# of headers.
defp extract_headers([%Translation{msgid: id, msgstr: headers}|rest])
when id == "" or id == [""],
do: {headers, rest}
defp extract_headers(translations),
do: {[], translations}
defp check_for_duplicates(translations) do
try do
Enum.reduce translations, HashDict.new, fn(t, acc) ->
id = translation_id(t)
if old_line = Dict.get(acc, id) do
throw({t, old_line})
else
Dict.put_new(acc, id, t.po_source_line)
end
end
:ok
catch
{t, old_line} ->
build_duplicated_error(t, old_line)
end
end
defp translation_id(%Translation{msgid: id}),
do: id
defp translation_id(%PluralTranslation{msgid: id, msgid_plural: idp}),
do: {id, idp}
defp build_duplicated_error(%Translation{} = t, old_line) do
id = IO.iodata_to_binary(t.msgid)
{:error, t.po_source_line, "found duplicate on line #{old_line} for msgid: '#{id}'"}
end
defp build_duplicated_error(%PluralTranslation{} = t, old_line) do
id = IO.iodata_to_binary(t.msgid)
idp = IO.iodata_to_binary(t.msgid_plural)
msg = "found duplicate on line #{old_line} for msgid: '#{id}' and msgid_plural: '#{idp}'"
{:error, t.po_source_line, msg}
end
end