Current section

Files

Jump to
gettext lib mix tasks gettext.merge.ex
Raw

lib/mix/tasks/gettext.merge.ex

defmodule Mix.Tasks.Gettext.Merge do
use Mix.Task
@recursive true
@shortdoc "Merge template files into translation files"
@moduledoc """
Merges template files (`.pot` files) into translation files (`.po` files).
If two files are given as arguments, they must be a `.po` and a `.po`/`.pot`
file). The first one is the old PO file, while the second one is the last
generated one. They are merged and written over the first file.
mix gettext.merge priv/gettext/en/LC_MESSAGES/default.pot priv/gettext/default.pot
If one file is given as an argument, then that file must be a directory
containing gettext translations (with `.pot` files at the root level alongside
locale directories).
mix gettext.merge DIR
If the `--locale LOCALE` option is given, then only the PO files in
`DIR/LOCALE/LC_MESSAGES` will be merged with the POT files in `DIR`. If no
options are given, then all the PO files for all locales under `DIR` are
merged with the POT files in `DIR`.
"""
alias Gettext.Merger
def run(args) do
_ = Mix.Project.get!
case OptionParser.parse(args, strict: [locale: :string]) do
{opts, [arg1, arg2], _} ->
run_with_two_args(arg1, arg2, opts)
{opts, [arg], _} ->
run_with_one_arg(arg, opts)
{_, [], _} ->
Mix.raise "gettext.merge requires at least one argument to work." <>
"Use `mix help gettext.merge` to see the usage of this task."
{_, _, _} ->
Mix.raise "Too many arguments for the gettext.merge task. " <>
"Use `mix help gettext.merge` to see the usage of this task."
end
end
defp run_with_two_args(arg1, arg2, []) do
if Path.extname(arg1) == ".po" and Path.extname(arg2) in [".po", ".pot"] do
ensure_file_exists!(arg1)
ensure_file_exists!(arg2)
{path, contents} = merge_po_with_pot(arg1, arg2)
File.write!(path, contents)
Mix.shell.info "Wrote #{path}"
else
Mix.raise "Arguments must be a PO file and a PO/POT file"
end
end
defp run_with_one_arg(arg, opts) do
ensure_dir_exists!(arg)
if locale = opts[:locale] do
merge_locale_dir(arg, locale)
else
merge_all_locale_dirs(arg)
end
end
defp merge_po_with_pot(po_file, pot_file) do
{po_file, Merger.merge_files(po_file, pot_file)}
end
defp merge_locale_dir(pot_dir, locale) do
locale_dir = locale_dir(pot_dir, locale)
ensure_dir_exists!(locale_dir)
merge_dirs(locale_dir, pot_dir)
end
defp merge_all_locale_dirs(pot_dir) do
pot_dir
|> ls_locale_dirs
|> Enum.each(&merge_dirs(&1, pot_dir))
end
defp locale_dir(dir, locale) do
Path.join([dir, locale, "LC_MESSAGES"])
end
defp ls_locale_dirs(dir) do
dir
|> File.ls!
|> Enum.filter(&File.dir?(Path.join(dir, &1)))
|> Enum.map(&locale_dir(dir, &1))
end
defp merge_dirs(po_dir, pot_dir) do
pot_dir
|> Path.join("**/*.pot")
|> Path.wildcard()
|> Enum.map(&find_matching_po(&1, po_dir))
|> Enum.map(&merge_or_create/1)
|> Enum.each(&write_file/1)
# Now warn for every PO file that has no matching POT file.
po_dir
|> Path.join("**/*.po")
|> Path.wildcard()
|> Enum.reject(&po_has_matching_pot?(&1, pot_dir))
|> Enum.map(&warn_for_missing_pot_file(&1, pot_dir))
end
defp find_matching_po(pot_file, po_dir) do
domain = Path.basename(pot_file, ".pot")
{pot_file, Path.join(po_dir, "#{domain}.po")}
end
defp merge_or_create({pot_file, po_file}) do
if File.regular?(po_file) do
{po_file, Merger.merge_files(po_file, pot_file)}
else
{po_file, Merger.new_po_file(po_file, pot_file)}
end
end
defp write_file({path, contents}) do
File.write!(path, contents)
Mix.shell.info "Wrote #{path}"
end
defp po_has_matching_pot?(po_file, pot_dir) do
domain = Path.basename(po_file, ".po")
pot_path = Path.join(pot_dir, "#{domain}.pot")
File.exists?(pot_path)
end
defp warn_for_missing_pot_file(po_file, pot_dir) do
Mix.shell.info "Warning: PO file #{po_file} has no matching POT file in #{pot_dir}"
end
defp ensure_file_exists!(path) do
unless File.regular?(path), do: Mix.raise("No such file: #{path}")
end
defp ensure_dir_exists!(path) do
unless File.dir?(path), do: Mix.raise("No such directory: #{path}")
end
end