localisation@

This commit is contained in:
Mayel 2021-06-09 13:42:40 +02:00
parent 22013eb235
commit 6eb4d3d6ce
13 changed files with 341 additions and 206 deletions

1
.gitignore vendored
View file

@ -70,3 +70,4 @@ deploy
# we use pnpm, so ignore others
assets/package-lock.json
assets/yarn.lock
priv/localisation/*

View file

@ -355,8 +355,8 @@ endif
licenses: init
@make --no-print-directory mix.remote~licenses
localise.extract: init
@make --no-print-directory mix~"gettext.extract --merge"
localise.extract:
@make --no-print-directory mix~"bonfire.localise.extract --merge"
assets.prepare:
cp lib/*/*/overlay/* rel/overlays/ 2> /dev/null || true

View file

@ -23,7 +23,7 @@ config :bonfire,
user_schema: Bonfire.Data.Identity.User,
org_schema: Bonfire.Data.Identity.User,
home_page: Bonfire.Web.HomeLive,
localisation_path: Path.expand("priv/localisation", File.cwd!()),
localisation_path: "priv/localisation",
ap_base_path: System.get_env("AP_BASE_PATH", "/pub"),
signing_salt: "this-will-be-overriden-by-a-secure-string-in-runtime.exs",
encryption_salt: "this-will-be-overriden-by-a-secure-string-in-runtime.exs"

View file

@ -1,11 +1,11 @@
# web
phoenix_live_view = "~> 0.14"
phoenix_html = "~> 2.11"
phoenix_live_dashboard = "~> 0.2.0"
phoenix_live_dashboard = "~> 0.4"
plug_cowboy = "~> 2.0"
phoenix = "~> 1.5.3"
phoenix_ecto = "~> 4.1"
gettext = "~> 0.11"
gettext = "~> 0.18"
jason = "~> 1.0"
# poison = "~> 4.0"
# db

View file

@ -29,7 +29,7 @@ then
else
set -e
#echo No local changes since last run
if [ $2 == 'pull' ]
if [[ $2 == 'pull' ]]
then
git pull
fi

View file

@ -0,0 +1,231 @@
defmodule Mix.Tasks.Bonfire.Dep.Compile do
use Mix.Task
@shortdoc "Compiles dependencies"
@moduledoc """
Compiles dependencies.
By default, compile all dependencies. A list of dependencies
can be given compile multiple dependencies in order.
This task attempts to detect if the project contains one of
the following files and act accordingly:
* `mix.exs` - invokes `mix compile`
* otherwise skip
If a list of dependencies is given, Mix will attempt to compile
them as is. For example, if project `a` depends on `b`, calling
`mix deps.compile a` will compile `a` even if `b` is out of
date. This is to allow parts of the dependency tree to be
recompiled without propagating those changes upstream. To ensure
`b` is included in the compilation step, pass `--include-children`.
"""
import Mix.Dep, only: [available?: 1, mix?: 1]
@switches [include_children: :boolean, force: :boolean]
@spec run(OptionParser.argv) :: :ok
def run(args) do
unless "--no-archives-check" in args do
Mix.Task.run "archive.check", args
end
Mix.Project.get!
case OptionParser.parse(args, switches: @switches) do
{opts, [], _} ->
# Because this command may be invoked explicitly with
# deps.compile, we simply try to compile any available
# dependency.
compile(Enum.filter(loaded_deps(), &available?/1), opts)
{opts, tail, _} ->
compile(loaded_by_name(tail, [env: Mix.env] ++ opts), opts)
end
end
@doc false
def compile(deps, options \\ []) do
shell = Mix.shell
config = Mix.Project.deps_config
Mix.Task.run "deps.precompile"
compiled =
Enum.map(deps, fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
check_unavailable!(app, status)
compiled? = cond do
mix?(dep) ->
maybe_clean(dep, options)
do_mix dep, config
true ->
shell.error "Could not compile #{inspect app}, no \"mix.exs\" found " <>
"(pass :compile as an option to customize compilation, set it to \"false\" to do nothing)"
false
end
# We should touch fetchable dependencies even if they
# did not compile otherwise they will always be marked
# as stale, even when there is nothing to do.
fetchable? = touch_fetchable(scm, opts[:build])
compiled? and fetchable?
end)
if true in compiled, do: Mix.Task.run("will_recompile"), else: :ok
end
defp maybe_clean(dep, opts) do
# If a dependency was marked as fetched or with an out of date lock
# or missing the app file, we always compile it from scratch.
if Keyword.get(opts, :force, false) or Mix.Dep.compilable?(dep) do
File.rm_rf!(Path.join([Mix.Project.build_path(), "lib", Atom.to_string(dep.app)]))
end
end
defp touch_fetchable(scm, path) do
if scm.fetchable? do
File.mkdir_p!(path)
File.touch!(Path.join(path, ".compile.fetch"))
true
else
false
end
end
defp check_unavailable!(app, {:unavailable, _}) do
Mix.raise "Cannot compile dependency #{inspect app} because " <>
"it isn't available, run \"mix deps.get\" first"
end
defp check_unavailable!(_, _) do
:ok
end
defp do_mix(dep, _config) do
Mix.Dep.in_dependency dep, fn _ ->
if req = old_elixir_req(Mix.Project.config) do
Mix.shell.error "warning: the dependency #{inspect dep.app} requires Elixir #{inspect req} " <>
"but you are running on v#{System.version}"
end
Mix.shell.info "Recompiling extension #{inspect dep.app}"
try do
# If "compile" was never called, the reenabling is a no-op and
# "compile.elixir" is a no-op as well (because it wasn't reenabled after
# running "compile"). If "compile" was already called, then running
# "compile" is a no-op and running "compile.elixir" will work because we
# manually reenabled it.
Mix.Task.reenable("compile.elixir")
Mix.Task.reenable("compile.leex")
Mix.Task.reenable("compile.all")
Mix.Task.reenable("compile")
options = [
# "--force",
"--no-deps-loading",
"--no-apps-loading",
"--no-archives-check",
"--no-elixir-version-check",
"--no-warnings-as-errors"
]
res = Mix.Task.run("compile", options)
# Mix.shell.info(inspect res)
match?({:ok, _}, res)
catch
kind, reason ->
stacktrace = System.stacktrace
app = dep.app
Mix.shell.error "could not compile dependency #{inspect app}, \"mix compile\" failed. " <>
"You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
"with \"mix deps.update #{app}\" or clean it with \"mix deps.clean #{app}\""
:erlang.raise(kind, reason, stacktrace)
end
end
end
defp old_elixir_req(config) do
req = config[:elixir]
if req && not Version.match?(System.version, req) do
req
end
end
defp loaded_deps() do
if Keyword.has_key?(Mix.Dep.__info__(:functions), :cached) do
Mix.Dep.cached()
else
Mix.Dep.loaded()
end
end
@doc """
Receives a list of dependency names and returns loaded `Mix.Dep`s.
Logs a message if the dependency could not be found.
## Exceptions
This function raises an exception if any of the dependencies
provided in the project are in the wrong format.
"""
def loaded_by_name(given, all_deps \\ nil, opts) do
all_deps = all_deps || loaded_deps()
# Ensure all apps are atoms
apps = to_app_names(given)
deps =
if opts[:include_children] do
get_deps_with_children(all_deps, apps)
else
get_deps(all_deps, apps)
end
Enum.each apps, fn(app) ->
unless Enum.any?(all_deps, &(&1.app == app)) do
Mix.raise "Unknown dependency #{app} for environment #{Mix.env}"
end
end
deps
end
defp to_app_names(given) do
Enum.map given, fn(app) ->
if is_binary(app), do: String.to_atom(app), else: app
end
end
defp get_deps(all_deps, apps) do
Enum.filter(all_deps, &(&1.app in apps))
end
defp get_deps_with_children(all_deps, apps) do
deps = get_children(all_deps, apps)
apps = deps |> Enum.map(& &1.app) |> Enum.uniq
get_deps(all_deps, apps)
end
defp get_children(_all_deps, []), do: []
defp get_children(all_deps, apps) do
# Current deps
deps = get_deps(all_deps, apps)
# Children apps
apps = for %{deps: children} <- deps,
%{app: app} <- children,
do: app
# Current deps + children deps
deps ++ get_children(all_deps, apps)
end
end

View file

@ -0,0 +1,94 @@
defmodule Mix.Tasks.Bonfire.Localise.Extract do
use Mix.Task
@recursive true
@shortdoc "Extracts translations from source code"
@moduledoc """
Extracts translations by recompiling the Elixir source code.
mix gettext.extract [OPTIONS]
Translations are extracted into POT (Portable Object Template) files (with a
`.pot` extension). The location of these files is determined by the `:otp_app`
and `:priv` options given by Gettext modules when they call `use Gettext`. One
POT file is generated for each translation domain.
It is possible to give the `--merge` option to perform merging
for every Gettext backend updated during merge:
mix gettext.extract --merge
All other options passed to `gettext.extract` are forwarded to the
`gettext.merge` task (`Mix.Tasks.Gettext.Merge`), which is called internally
by this task. For example:
mix gettext.extract --merge --no-fuzzy
"""
@switches [merge: :boolean]
def run(args, app \\ :bonfire_common) do
Application.ensure_all_started(:gettext)
_ = Mix.Project.get!()
mix_config = Mix.Project.config()
{opts, _} = OptionParser.parse!(args, switches: @switches)
pot_files = extract(app || mix_config[:app], mix_config[:gettext] || []) #|> IO.inspect(label: "extracted")
for {path, contents} <- pot_files do
File.mkdir_p!(Path.dirname(path))
File.write!(path, contents)
Mix.shell().info("Extracted #{Path.relative_to_cwd(path)}")
end
if opts[:merge] do
run_merge(pot_files, args)
end
:ok
end
defp extract(app, gettext_config) do
Gettext.Extractor.enable()
force_compile()
Gettext.Extractor.pot_files(
app, # |> IO.inspect(label: "app"),
gettext_config # |> IO.inspect(label: "config")
)
after
Gettext.Extractor.disable()
end
defp force_compile do
Mix.Tasks.Compile.Elixir.manifests()
# ++ (Mix.Project.build_path<>"/lib/bonfire_*/.mix/compile.elixir" |> IO.inspect |> Path.wildcard(match_dot: true))
# ["forks/bonfire_common/lib/web/gettext.ex" |> Path.expand(File.cwd!)]
#|> IO.inspect(label: "recompile")
|> Enum.map(&make_old_if_exists/1)
(["--force"] ++ Bonfire.MixProject.deps_to_localise()) #|> IO.inspect
|> Mix.Tasks.Bonfire.Dep.Compile.run()
# If "compile" was never called, the reenabling is a no-op and
# "compile.elixir" is a no-op as well (because it wasn't reenabled after
# running "compile"). If "compile" was already called, then running
# "compile" is a no-op and running "compile.elixir" will work because we
# manually reenabled it.
Mix.Task.reenable("compile.elixir")
Mix.Task.run("compile")
Mix.Task.run("compile.elixir")
end
defp make_old_if_exists(path) do
:file.change_time(path, {{2000, 1, 1}, {0, 0, 0}})
end
defp run_merge(pot_files, argv) do
pot_files
|> Enum.map(fn {path, _} -> Path.dirname(path) end)
|> Enum.uniq()
|> Task.async_stream(&Mix.Tasks.Gettext.Merge.run([&1 | argv]), ordered: false)
|> Stream.run()
end
end

View file

@ -4,16 +4,13 @@ defmodule Bonfire.GraphQL.Schema do
@moduledoc "Root GraphQL Schema"
use Absinthe.Schema
@schema_provider Absinthe.Schema.PersistentTerm
# import Bonfire.GraphQL.SchemaUtils
# @pipeline_modifier Bonfire.GraphQL.SchemaPipelines
require Logger
alias Bonfire.GraphQL.SchemaUtils
alias Bonfire.GraphQL.Middleware.CollapseErrors
alias Absinthe.Middleware.{Async, Batch}
# @pipeline_modifier OverridePhase
def plugins, do: [Async, Batch]

View file

@ -2,7 +2,7 @@ defmodule Bonfire.Web.HomeLive do
use Bonfire.Web, {:live_view, [layout: {Bonfire.Web.LayoutView, "without_sidebar.html"}]}
alias Bonfire.Web.LivePlugs
import Bonfire.Web.Gettext
def mount(params, session, socket) do
LivePlugs.live_plug params, session, socket, [

View file

@ -10,8 +10,7 @@
<%= Bonfire.Common.Config.get([:ui, :theme, :instance_description], "Yet another bonfire instance") %>
</p>
<%= e(assigns, :locale, "") %>
<%= l("Hello") %>
</div>
</div>
</div>

View file

@ -101,7 +101,7 @@ defmodule Bonfire.MixProject do
end
defp deps(deps \\ deps(), deps_subtype) when is_atom(deps_subtype), do:
def deps(deps \\ deps(), deps_subtype) when is_atom(deps_subtype), do:
Enum.filter(deps, &include_dep?(deps_subtype, &1))
def flavour_path(), do:
@ -134,6 +134,11 @@ defmodule Bonfire.MixProject do
|> IO.inspect(label: "Running Bonfire #{version()} with configuration from #{flavour_path()} in #{Mix.env()} environment. You can run `mix bonfire.deps.update` to update these extensions and dependencies")
end
def deps_to_localise() do
deps(:test)
|> Enum.map(&dep_name/1)
end
# Specifies which paths to include when running tests
defp test_paths(), do: ["test" | Enum.flat_map(deps(:test), &dep_paths(&1, "test"))]

View file

@ -1,97 +0,0 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""

View file

@ -1,95 +0,0 @@
## This is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""