Repository: phoenixframework/phoenix
Pull request #3387 (2019-04-26 16:03:22)

Add `--no-gettext` flag to `phx.new` task
Usage example: `mix phx.new phx_blog --no-gettext`

When a Phoenix app is created with `--no-gettext`:
- gettext files are not generated (`gettext.ex` and `priv/gettext/*`)
- gettext dependency is not required in `mix.exs`
- gettext related code is not generated

Resolves: https://github.com/phoenixframework/phoenix/issues/1955

josevalim: So [this bug](https://github.com/elixir-ecto/ecto/issues/2991) actually blocks this PR as the generated code as an alternative to Gettext will fail for some validations.
manelli: Please let me know if there's anything I can do for this PR to be merged.
Thank you very much!
chrismccord: Just one small change and this is good to go. Thanks!

Patch:
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index 12030f85c..6fb809a7b 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -38,6 +38,8 @@ defmodule Mix.Tasks.Phx.New do
 
     * `--no-html` - do not generate HTML views.
 
+    * `--no-gettext` - do not generate gettext files.
+
     * `--binary-id` - use `binary_id` as primary key type
       in Ecto schemas
 
@@ -93,7 +95,7 @@ defmodule Mix.Tasks.Phx.New do
   @switches [dev: :boolean, webpack: :boolean, ecto: :boolean,
              app: :string, module: :string, web_module: :string,
              database: :string, binary_id: :boolean, html: :boolean,
-             umbrella: :boolean, verbose: :boolean]
+             gettext: :boolean, umbrella: :boolean, verbose: :boolean]
 
   def run([version]) when version in ~w(-v --version) do
     Mix.shell.info("Phoenix v#{@version}")
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index 5793e6b16..2c06c3060 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -110,6 +110,7 @@ defmodule Phx.New.Generator do
     db           = Keyword.get(opts, :database, "postgres")
     ecto         = Keyword.get(opts, :ecto, true)
     html         = Keyword.get(opts, :html, true)
+    gettext      = Keyword.get(opts, :gettext, true)
     webpack      = Keyword.get(opts, :webpack, true)
     dev          = Keyword.get(opts, :dev, false)
     phoenix_path = phoenix_path(project, dev)
@@ -154,6 +155,7 @@ defmodule Phx.New.Generator do
       webpack: webpack,
       ecto: ecto,
       html: html,
+      gettext: gettext,
       adapter_app: adapter_app,
       adapter_module: adapter_module,
       adapter_config: adapter_config,
diff --git a/installer/lib/phx_new/project.ex b/installer/lib/phx_new/project.ex
index 7ccdcb17a..e59d85215 100644
--- a/installer/lib/phx_new/project.ex
+++ b/installer/lib/phx_new/project.ex
@@ -39,6 +39,10 @@ defmodule Phx.New.Project do
     Keyword.fetch!(binding, :html)
   end
 
+  def gettext?(%Project{binding: binding}) do
+    Keyword.fetch!(binding, :gettext)
+  end
+
   def webpack?(%Project{binding: binding}) do
     Keyword.fetch!(binding, :webpack)
   end
diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex
index 073a94e84..7d175f019 100644
--- a/installer/lib/phx_new/single.ex
+++ b/installer/lib/phx_new/single.ex
@@ -105,10 +105,10 @@ defmodule Phx.New.Single do
 
   def generate(%Project{} = project) do
     copy_from project, __MODULE__, :new
-    copy_from project, __MODULE__, :gettext
 
     if Project.ecto?(project), do: gen_ecto(project)
     if Project.html?(project), do: gen_html(project)
+    if Project.gettext?(project), do: gen_gettext(project)
 
     case {Project.webpack?(project), Project.html?(project)} do
       {true, _}      -> gen_webpack(project)
@@ -123,6 +123,10 @@ defmodule Phx.New.Single do
     copy_from project, __MODULE__, :html
   end
 
+  def gen_gettext(project) do
+    copy_from project, __MODULE__, :gettext
+  end
+
   def gen_ecto(project) do
     copy_from project, __MODULE__, :ecto
     gen_ecto_config(project)
diff --git a/installer/lib/phx_new/web.ex b/installer/lib/phx_new/web.ex
index 9e58c1e63..eb8a68ce1 100644
--- a/installer/lib/phx_new/web.ex
+++ b/installer/lib/phx_new/web.ex
@@ -64,9 +64,9 @@ defmodule Phx.New.Web do
   def generate(%Project{} = project) do
     inject_umbrella_config_defaults(project)
     copy_from project, __MODULE__, :new
-    copy_from project, __MODULE__, :gettext
 
     if Project.html?(project), do: gen_html(project)
+    if Project.gettext?(project), do: gen_gettext(project)
 
     case {Project.webpack?(project), Project.html?(project)} do
       {true, _}      -> Phx.New.Single.gen_webpack(project)
@@ -80,4 +80,8 @@ defmodule Phx.New.Web do
   defp gen_html(%Project{} = project) do
     copy_from project, __MODULE__, :html
   end
+
+  defp gen_gettext(%Project{} = project) do
+    copy_from project, __MODULE__, :gettext
+  end
 end
diff --git a/installer/templates/phx_single/config/dev.exs b/installer/templates/phx_single/config/dev.exs
index c44285494..3b6657c54 100644
--- a/installer/templates/phx_single/config/dev.exs
+++ b/installer/templates/phx_single/config/dev.exs
@@ -49,8 +49,8 @@ config :<%= app_name %>, <%= endpoint_module %>,
 config :<%= app_name %>, <%= endpoint_module %>,
   live_reload: [
     patterns: [
-      ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
-      ~r"priv/gettext/.*(po)$",
+      ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",<%= if gettext do %>
+      ~r"priv/gettext/.*(po)$",<% end %>
       ~r"lib/<%= lib_web_name %>/(live|views)/.*(ex)$",
       ~r"lib/<%= lib_web_name %>/templates/.*(eex)$"
     ]
diff --git a/installer/templates/phx_single/lib/app_name_web.ex b/installer/templates/phx_single/lib/app_name_web.ex
index 9b8db915d..3d483ebe4 100644
--- a/installer/templates/phx_single/lib/app_name_web.ex
+++ b/installer/templates/phx_single/lib/app_name_web.ex
@@ -21,8 +21,8 @@ defmodule <%= web_namespace %> do
     quote do
       use Phoenix.Controller, namespace: <%= web_namespace %>
 
-      import Plug.Conn
-      import <%= web_namespace %>.Gettext
+      import Plug.Conn<%= if gettext do %>
+      import <%= web_namespace %>.Gettext<% end %>
       alias <%= web_namespace %>.Router.Helpers, as: Routes
     end
   end
@@ -39,8 +39,8 @@ defmodule <%= web_namespace %> do
       # Use all HTML functionality (forms, tags, etc)
       use Phoenix.HTML<% end %>
 
-      import <%= web_namespace %>.ErrorHelpers
-      import <%= web_namespace %>.Gettext
+      import <%= web_namespace %>.ErrorHelpers<%= if gettext do %>
+      import <%= web_namespace %>.Gettext<% end %>
       alias <%= web_namespace %>.Router.Helpers, as: Routes
     end
   end
@@ -55,8 +55,8 @@ defmodule <%= web_namespace %> do
 
   def channel do
     quote do
-      use Phoenix.Channel
-      import <%= web_namespace %>.Gettext
+      use Phoenix.Channel<%= if gettext do %>
+      import <%= web_namespace %>.Gettext<% end %>
     end
   end
 
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index 3d55fc1db..6f539d93f 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -11,7 +11,7 @@ defmodule <%= app_module %>.MixProject do
       lockfile: "../../mix.lock",<% end %>
       elixir: "~> 1.5",
       elixirc_paths: elixirc_paths(Mix.env()),
-      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
+      compilers: [:phoenix<%= if gettext do %>, :gettext<% end %>] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,<%= if ecto do %>
       aliases: aliases(),<% end %>
       deps: deps()
@@ -42,8 +42,8 @@ defmodule <%= app_module %>.MixProject do
       {:ecto_sql, "~> 3.1"},
       {<%= inspect adapter_app %>, ">= 0.0.0"},<% end %><%= if html do %>
       {:phoenix_html, "~> 2.11"},
-      {:phoenix_live_reload, "~> 1.2", only: :dev},<% end %>
-      {:gettext, "~> 0.11"},
+      {:phoenix_live_reload, "~> 1.2", only: :dev},<% end %><%= if gettext do %>
+      {:gettext, "~> 0.11"},<% end %>
       {:jason, "~> 1.0"},
       {:plug_cowboy, "~> 2.0"}
     ]
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
index 7fbda1292..421166791 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
@@ -47,8 +47,8 @@ config :<%= web_app_name %>, <%= endpoint_module %>,
 config :<%= web_app_name %>, <%= endpoint_module %>,
   live_reload: [
     patterns: [
-      ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
-      ~r"priv/gettext/.*(po)$",
+      ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",<%= if gettext do %>
+      ~r"priv/gettext/.*(po)$",<% end %>
       ~r"lib/<%= web_app_name %>/(live|views)/.*(ex)$",
       ~r"lib/<%= web_app_name %>/templates/.*(eex)$"
     ]
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex
index 6c64ccbcf..bb0254708 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex
+++ b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex
@@ -20,8 +20,8 @@ defmodule <%= web_namespace %> do
   def controller do
     quote do
       use Phoenix.Controller, namespace: <%= web_namespace %>
-      import Plug.Conn
-      import <%= web_namespace %>.Gettext
+      import Plug.Conn<%= if gettext do %>
+      import <%= web_namespace %>.Gettext<% end %>
       alias <%= web_namespace %>.Router.Helpers, as: Routes
     end
   end
@@ -38,8 +38,8 @@ defmodule <%= web_namespace %> do
       # Use all HTML functionality (forms, tags, etc)
       use Phoenix.HTML<% end %>
 
-      import <%= web_namespace %>.ErrorHelpers
-      import <%= web_namespace %>.Gettext
+      import <%= web_namespace %>.ErrorHelpers<%= if gettext do %>
+      import <%= web_namespace %>.Gettext<% end %>
       alias <%= web_namespace %>.Router.Helpers, as: Routes
     end
   end
@@ -54,8 +54,8 @@ defmodule <%= web_namespace %> do
 
   def channel do
     quote do
-      use Phoenix.Channel
-      import <%= web_namespace %>.Gettext
+      use Phoenix.Channel<%= if gettext do %>
+      import <%= web_namespace %>.Gettext<% end %>
     end
   end
 
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
index 9b3c9cd1d..071ba88e5 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
@@ -11,7 +11,7 @@ defmodule <%= web_namespace %>.MixProject do
       lockfile: "../../mix.lock",
       elixir: "~> 1.5",
       elixirc_paths: elixirc_paths(Mix.env()),
-      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
+      compilers: [:phoenix<%= if gettext do %>, :gettext<% end %>] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,
       aliases: aliases(),
       deps: deps()
@@ -40,8 +40,8 @@ defmodule <%= web_namespace %>.MixProject do
       <%= phoenix_dep %>,<%= if ecto do %>
       {:phoenix_ecto, "~> 4.0"},<% end %><%= if html do %>
       {:phoenix_html, "~> 2.11"},
-      {:phoenix_live_reload, "~> 1.2", only: :dev},<% end %>
-      {:gettext, "~> 0.11"},<%= if app_name != web_app_name do %>
+      {:phoenix_live_reload, "~> 1.2", only: :dev},<% end %><%= if gettext do %>
+      {:gettext, "~> 0.11"},<% end %><%= if app_name != web_app_name do %>
       {:<%= app_name %>, in_umbrella: true},<% end %>
       {:jason, "~> 1.0"},
       {:plug_cowboy, "~> 2.0"}
diff --git a/installer/templates/phx_web/templates/page/index.html.eex b/installer/templates/phx_web/templates/page/index.html.eex
index 07a2ae6bd..e52c1bb43 100644
--- a/installer/templates/phx_web/templates/page/index.html.eex
+++ b/installer/templates/phx_web/templates/page/index.html.eex
@@ -1,5 +1,6 @@
-<section class="phx-hero">
-  <h1><%%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
+<section class="phx-hero"><%= if gettext do %>
+  <h1><%%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1><% else %>
+  <h1>Welcome to Phoenix!</h1><% end %>
   <p>A productive web framework that<br/>does not compromise speed or maintainability.</p>
 </section>
 
diff --git a/installer/templates/phx_web/views/error_helpers.ex b/installer/templates/phx_web/views/error_helpers.ex
index dfd0e75a9..2123ef594 100644
--- a/installer/templates/phx_web/views/error_helpers.ex
+++ b/installer/templates/phx_web/views/error_helpers.ex
@@ -1,8 +1,8 @@
 defmodule <%= web_namespace %>.ErrorHelpers do
   @moduledoc """
   Conveniences for translating and building error messages.
-  """
-<%= if html do %>
+  """<%= if html do %>
+
   use Phoenix.HTML
 
   @doc """
@@ -12,8 +12,8 @@ defmodule <%= web_namespace %>.ErrorHelpers do
     Enum.map(Keyword.get_values(form.errors, field), fn error ->
       content_tag(:span, translate_error(error), class: "help-block")
     end)
-  end
-<% end %>
+  end<% end %><%= if gettext do %>
+
   @doc """
   Translates an error message using gettext.
   """
@@ -40,5 +40,16 @@ defmodule <%= web_namespace %>.ErrorHelpers do
     else
       Gettext.dgettext(<%= web_namespace %>.Gettext, "errors", msg, opts)
     end
-  end
+  end<% else %>
+
+  @doc """
+  Translates an error message.
+  """
+  def translate_error({msg, opts}) do
+    # Because the error messages we show in our forms and APIs
+    # are defined inside Ecto, we need to translate them dynamically.
+    Enum.reduce(opts, msg, fn {key, value}, acc ->
+      String.replace(acc, "%{#{key}}", to_string(value))
+    end)
+  end<% end %>
 end


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index bba2b674c..fc126583a 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -160,7 +160,7 @@ defmodule Mix.Tasks.Phx.NewTest do
 
   test "new without defaults" do
     in_tmp "new without defaults", fn ->
-      Mix.Tasks.Phx.New.run([@app_name, "--no-html", "--no-webpack", "--no-ecto"])
+      Mix.Tasks.Phx.New.run([@app_name, "--no-html", "--no-webpack", "--no-ecto", "--no-gettext"])
 
       # No webpack
       refute File.read!("phx_blog/.gitignore") |> String.contains?("/assets/node_modules/")
@@ -179,6 +179,15 @@ defmodule Mix.Tasks.Phx.NewTest do
       config = ~r/config :phx_blog, PhxBlog.Repo,/
       refute File.exists?("phx_blog/lib/phx_blog/repo.ex")
 
+      # No gettext
+      refute_file "phx_blog/lib/phx_blog_web/gettext.ex"
+      refute_file "phx_blog/priv/gettext/en/LC_MESSAGES/errors.po"
+      refute_file "phx_blog/priv/gettext/errors.pot"
+      assert_file "phx_blog/mix.exs", &refute(&1 =~ ~r":gettext")
+      assert_file "phx_blog/lib/phx_blog_web.ex", &refute(&1 =~ ~r"import AmsMockWeb.Gettext")
+      assert_file "phx_blog/lib/phx_blog_web/views/error_helpers.ex", &refute(&1 =~ ~r"gettext")
+      assert_file "phx_blog/config/dev.exs", &refute(&1 =~ ~r"gettext")
+
       assert_file "phx_blog/.formatter.exs", fn file ->
         assert file =~ "import_deps: [:phoenix]"
         assert file =~ "inputs: [\"*.{ex,exs}\", \"{config,lib,test}/**/*.{ex,exs}\"]"