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.ex
defmodule Gettext.PO do
@moduledoc """
This module provides facilities for working with `.po` files (mainly parsing).
"""
alias Gettext.PO
alias Gettext.PO.Tokenizer
alias Gettext.PO.Parser
alias Gettext.PO.SyntaxError
alias Gettext.PO.Translation
alias Gettext.PO.PluralTranslation
@type line :: pos_integer
@type parse_error :: {:error, line, binary}
@type translation :: Translation.t | PluralTranslation.t
defstruct headers: [], translations: [], file: nil
@type t :: %__MODULE__{
headers: [binary],
translations: [translation],
file: Path.t,
}
@doc """
Parses a string into a list of translations.
This function parses a given `str` into a list of `Gettext.PO.Translation` and
`Gettext.PO.PluralTranslation` structs. It returns `{:ok, translations}` if
there are no errors, otherwise `{:error, line, reason}`.
## Examples
iex> {:ok, po} = Gettext.PO.parse_string \"""
...> msgid "foo"
...> msgstr "bar"
...> \"""
iex> [t] = po.translations
iex> t.msgid
["foo"]
iex> t.msgstr
["bar"]
iex> po.headers
[]
iex> Gettext.PO.parse_string "foo"
{:error, 1, "unknown keyword 'foo'"}
"""
@spec parse_string(binary) :: {:ok, t} | parse_error
def parse_string(str) do
case Tokenizer.tokenize(str) do
{:error, _line, _reason} = error ->
error
{:ok, tokens} ->
case Parser.parse(tokens) do
{:error, _line, _reason} = error ->
error
{:ok, headers, translations} ->
{:ok, %PO{headers: headers, translations: translations}}
end
end
end
@doc """
Parses a string into a list of translations, raising an exception if there are
any errors.
Works exactly like `parse_string/1`, but returns the list of translations
if there are no errors or raises a `Gettext.PO.SyntaxError` error if there
are.
## Examples
iex> Gettext.PO.parse_string!("msgid")
** (Gettext.PO.SyntaxError) 1: no space after 'msgid'
"""
@spec parse_string!(binary) :: t
def parse_string!(str) do
case parse_string(str) do
{:ok, parsed} ->
parsed
{:error, line, reason} ->
raise SyntaxError, line: line, reason: reason
end
end
@doc """
Parses the contents of a file into a list of translations.
This function works similarly to `parse_string/1` except that it takes a file
and parses the contents of that file. It can return:
* `{:ok, translations}`
* `{:error, line, reason}` if there is an error with the contents of the
`.po` file (e.g., a syntax error)
* `{:error, reason}` if there is an error with reading the file (this error
is one of the errors that can be returned by `File.read/1`)_
## Examples
Gettext.PO.parse_file "translations.po"
#=> {:ok, [%Translation{msgid: "foo", msgstr: "bar"}]}
Gettext.PO.parse_file "nonexistent"
#=> {:error, :enoent}
"""
@spec parse_file(Path.t) :: {:ok, t} | parse_error | {:error, atom}
def parse_file(path) do
case File.read(path) do
{:ok, contents} ->
case parse_string(contents) do
{:ok, po} -> {:ok, %{po | file: path}}
{:error, _line, _reason} = error -> error
end
{:error, _reason} = error ->
error
end
end
@doc """
Parses the contents of a file into a list of translations, raising if there
are any errors.
Works like `parse_file/1`, except that it raises a `Gettext.PO.SyntaxError`
exception if there's a syntax error in the file or a `File.Error` error if
there's an error with reading the file.
## Examples
Gettext.PO.parse_file! "nonexistent.po"
#=> ** (File.Error) could not parse file nonexistent.po: no such file or directory
"""
@spec parse_file!(Path.t) :: t
def parse_file!(path) do
case parse_file(path) do
{:ok, parsed} ->
parsed
{:error, reason} ->
raise File.Error, reason: reason, action: "parse", path: path
{:error, line, reason} ->
raise SyntaxError, file: path, line: line, reason: reason
end
end
@doc """
Dumps a `Gettext.PO` struct as iodata.
This function dumps a `Gettext.PO` struct (representing a PO file) as iodata,
which can later be written to a file or converted to a string with
`IO.iodata_to_binary/1`.
## Examples
After running the following code:
iodata = Gettext.PO.dump %Gettext.PO{
headers: ["Last-Translator: Jane Doe"],
translations: [
%Gettext.PO.Translation{msgid: "foo", msgstr: "bar", comments: "# A comment"}
]
}
File.write!("/tmp/test.po", iodata)
the `/tmp/test.po` file would look like this:
msgid ""
msgstr ""
"Last-Translator: Jane Doe"
# A comment
msgid "foo"
msgstr "bar"
"""
@spec dump(t) :: iodata
def dump(po)
def dump(%PO{headers: [], translations: translations}) do
dump_translations(translations)
end
def dump(%PO{headers: headers, translations: []}) do
dump_headers(headers)
end
def dump(%PO{headers: headers, translations: translations}) do
[dump_headers(headers), ?\n, dump_translations(translations)]
end
def dump_headers([]) do
[]
end
def dump_headers([first|_] = headers) when first != "" do
dump_headers([""|headers])
end
def dump_headers(headers) do
[
~s(msgid ""\n),
dump_kw_and_strings("msgstr", headers, 0),
]
end
defp dump_translations(translations) do
translations
|> Enum.map(&dump_translation/1)
|> Enum.intersperse(?\n)
end
defp dump_translation(%Translation{} = t) do
[
dump_comments(t.comments),
dump_flags(t.flags),
dump_references(t.references),
dump_kw_and_strings("msgid", t.msgid),
dump_kw_and_strings("msgstr", t.msgstr),
]
end
defp dump_translation(%PluralTranslation{} = t) do
[
dump_comments(t.comments),
dump_flags(t.flags),
dump_references(t.references),
dump_kw_and_strings("msgid", t.msgid),
dump_kw_and_strings("msgid_plural", t.msgid_plural),
dump_plural_msgstr(t.msgstr)
]
end
defp dump_comments(comments) do
Enum.map(comments, &[&1, ?\n])
end
defp dump_references(references) do
for {file, line} <- references do
"#: #{file}:#{line}\n"
end
end
defp dump_flags(flags) do
flags =
flags
|> Enum.sort
|> Enum.map(&[?\s, &1])
if flags == [], do: [], else: [?#, ?,, flags, ?\n]
end
defp dump_plural_msgstr(msgstr) do
Enum.map msgstr, fn {plural_form, str} ->
dump_kw_and_strings("msgstr[#{plural_form}]", str)
end
end
defp dump_kw_and_strings(keyword, [first|rest], indentation \\ 2) do
first = ~s(#{keyword} "#{escape(first)}"\n)
rest = Enum.map rest, &indent_line([?", escape(&1), ?", ?\n], indentation)
[first, rest]
end
defp indent_line(str, indentation, with \\ " ") do
[String.duplicate(with, indentation), str]
end
defp escape(str) do
for <<char <- str>>, into: "", do: escape_char(char)
end
defp escape_char(?"), do: ~S(\")
defp escape_char(?\n), do: ~S(\n)
defp escape_char(?\t), do: ~S(\t)
defp escape_char(?\r), do: ~S(\r)
defp escape_char(char), do: <<char>>
end