defmodule Mix.Tasks.Gettext.Pseudolocalize do use Mix.Task @shortdoc "Creates a pseudolocalized version of the xx locale" @moduledoc """ Creates a pseudolocalized version of the xx locale for testing internationalization. Pseudolocalization is a software testing method used to verify internationalization aspects of software. It replaces ordinary characters with accented, uncommon, or otherwise modified versions while maintaining the readability of the text. ## Usage First, extract your gettext strings: $ mix gettext.extract Then run the pseudolocalization: $ mix gettext.pseudolocalize This will process all .po files in the xx locale directory (priv/gettext/xx/LC_MESSAGES/). """ @impl Mix.Task def run(args) do # First extract and merge target_dir = case args do [custom_dir] -> custom_dir _ -> with_xx_pluralizer(fn -> Mix.Task.run("gettext.merge", ["priv/gettext", "--locale=xx"]) end) Path.join(["priv", "gettext", "xx", "LC_MESSAGES"]) end # Process all .po files in the target directory target_dir |> Path.join("*.po") |> Path.wildcard() |> Enum.each(&process_file(&1)) Mix.shell().info([:green, "✓ Pseudolocalization complete"]) end # gettext ~> 1.0 raises `Gettext.Plural.UnknownLocaleError` when creating a new # PO file for "xx" because the default `Gettext.Plural` has no entry for it. # Temporarily swap in `GettextPseudolocalize.Plural`, which knows about "xx" # and delegates everything else to the default, then restore the previous # configuration so we don't affect the host application. defp with_xx_pluralizer(fun) do previous = Application.get_env(:gettext, :plural_forms) Application.put_env(:gettext, :plural_forms, GettextPseudolocalize.Plural) try do fun.() after case previous do nil -> Application.delete_env(:gettext, :plural_forms) mod -> Application.put_env(:gettext, :plural_forms, mod) end end end defp process_file(file_path) do with {:ok, po} <- Expo.PO.parse_file(file_path), updated_po = update_messages(po) do content = Expo.PO.compose(updated_po) File.write!(file_path, content) Mix.shell().info("Processed #{Path.basename(file_path)}") end end defp update_messages(%Expo.Messages{} = po) do updated_messages = Enum.map(po.messages, &update_message/1) %{po | messages: updated_messages} end defp update_message(%Expo.Message.Singular{msgstr: [""]} = message) do %{message | msgstr: [pseudolocalize(message.msgid)]} end defp update_message(%Expo.Message.Plural{msgstr: msgstr} = message) when is_map(msgstr) do updated_msgstr = Map.new(msgstr, fn {index, [""]} when index == 0 -> {index, [pseudolocalize(message.msgid)]} {index, [""]} -> {index, [pseudolocalize(message.msgid_plural)]} other -> other end) %{message | msgstr: updated_msgstr} end defp update_message(message), do: message defp pseudolocalize([string]) when is_binary(string) do GettextPseudolocalize.Process.convert(string) end end