mirror of
https://github.com/bonfire-networks/bonfire-app.git
synced 2024-05-15 23:52:41 +00:00
localisation@
This commit is contained in:
parent
22013eb235
commit
6eb4d3d6ce
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -70,3 +70,4 @@ deploy
|
|||
# we use pnpm, so ignore others
|
||||
assets/package-lock.json
|
||||
assets/yarn.lock
|
||||
priv/localisation/*
|
||||
|
|
4
Makefile
4
Makefile
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
231
lib/mix/tasks/localise/dep.compile.ex
Normal file
231
lib/mix/tasks/localise/dep.compile.ex
Normal 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
|
94
lib/mix/tasks/localise/localise.extract.ex
Normal file
94
lib/mix/tasks/localise/localise.extract.ex
Normal 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
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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, [
|
||||
|
|
|
@ -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>
|
||||
|
|
7
mix.exs
7
mix.exs
|
@ -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"))]
|
||||
|
||||
|
|
|
@ -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 ""
|
|
@ -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 ""
|
Loading…
Reference in a new issue