From 560c2c1c8cd7f9d791400b4de58f9da8446b6b56 Mon Sep 17 00:00:00 2001 From: Ivan Reshetnikov Date: Tue, 9 Jul 2024 23:07:57 +0500 Subject: [PATCH] fix(flash): write custom styles, update translations --- assets/css/app.css | 6 + assets/css/core_components.css | 40 ------ assets/css/flash.css | 51 ++++++++ config/config.exs | 2 + lib/comfycamp_web.ex | 2 + .../components/core_components.ex | 115 +----------------- lib/comfycamp_web/components/flash.ex | 97 +++++++++++++++ lib/comfycamp_web/components/icons.ex | 58 +++++++++ mix.exs | 7 -- mix.lock | 1 - priv/gettext/default.pot | 53 ++++++++ priv/gettext/en/LC_MESSAGES/default.po | 53 ++++++++ priv/gettext/ru/LC_MESSAGES/default.po | 53 ++++++++ priv/gettext/ru/LC_MESSAGES/errors.po | 111 +++++++++++++++++ 14 files changed, 487 insertions(+), 162 deletions(-) create mode 100644 assets/css/flash.css create mode 100644 lib/comfycamp_web/components/flash.ex create mode 100644 lib/comfycamp_web/components/icons.ex create mode 100644 priv/gettext/default.pot create mode 100644 priv/gettext/en/LC_MESSAGES/default.po create mode 100644 priv/gettext/ru/LC_MESSAGES/default.po create mode 100644 priv/gettext/ru/LC_MESSAGES/errors.po diff --git a/assets/css/app.css b/assets/css/app.css index 4fcb28b..ab44006 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,4 +1,5 @@ @import "./core_components.css"; +@import "./flash.css"; :root { --bg: #13151a; @@ -46,3 +47,8 @@ footer { width: 100%; margin: auto; } + +.icon { + width: 20px; + height: 20px; +} diff --git a/assets/css/core_components.css b/assets/css/core_components.css index 787ecd4..71d7707 100644 --- a/assets/css/core_components.css +++ b/assets/css/core_components.css @@ -126,46 +126,6 @@ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } -.flash { - position: fixed; - top: 0.5rem; - right: 0.5rem; - z-index: 50; - padding: 0.75rem; - margin-right: 0.5rem; - border-radius: 0.5rem; - box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); - width: 20rem; -} - -@media (min-width: 640px) { - .flash { - width: 24rem; - } -} - -.flash-title { - display: flex; - gap: 0.375rem; - align-items: center; - font-size: 0.875rem; - font-weight: 600; - line-height: 1.5rem; -} - -.flash-close-button { - position: absolute; - top: 0.25rem; - right: 0.25rem; - padding: 0.5rem; -} - -.flash-close-button-icon { - width: 1.25rem; - height: 1.25rem; - opacity: 0.4; -} - .simple-form { margin-top: 2rem; background-color: #ffffff; diff --git a/assets/css/flash.css b/assets/css/flash.css new file mode 100644 index 0000000..7c39cef --- /dev/null +++ b/assets/css/flash.css @@ -0,0 +1,51 @@ +.flash { + position: fixed; + top: 0.5rem; + right: 0.5rem; + z-index: 50; + padding: 16px; + border-radius: 0.3rem; + box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + width: 20rem; +} + +.flash-info { + background-color: #15803d; +} + +.flash-error { + background-color: #b91c1c; +} + +@media (min-width: 640px) { + .flash { + width: 24rem; + } +} + +.flash-title { + margin: 0; + display: flex; + gap: 8px; + align-items: center; + font-weight: 600; +} + +.flash-body { + margin-bottom: 0; + text-indent: 28px; +} + +.flash-close-button { + padding: 0; + position: absolute; + width: 28px; + height: 28px; + top: 0.5rem; + right: 0.5rem; + cursor: pointer; +} + +.flash-close-button-icon { + opacity: 0.6; +} diff --git a/config/config.exs b/config/config.exs index 634e180..8bcbdc1 100644 --- a/config/config.exs +++ b/config/config.exs @@ -11,6 +11,8 @@ config :comfycamp, ecto_repos: [Comfycamp.Repo], generators: [timestamp_type: :utc_datetime] +config :comfycamp, ComfycampWeb.Gettext, locales: ~w(en ru), default_locale: "ru" + # Configures the endpoint config :comfycamp, ComfycampWeb.Endpoint, url: [host: "localhost"], diff --git a/lib/comfycamp_web.ex b/lib/comfycamp_web.ex index fca54d4..4cb7922 100644 --- a/lib/comfycamp_web.ex +++ b/lib/comfycamp_web.ex @@ -84,7 +84,9 @@ defmodule ComfycampWeb do # HTML escaping functionality import Phoenix.HTML # Core UI components and translation + import ComfycampWeb.Icons import ComfycampWeb.CoreComponents + import ComfycampWeb.Flash import ComfycampWeb.Gettext # Shortcut for generating JS commands diff --git a/lib/comfycamp_web/components/core_components.ex b/lib/comfycamp_web/components/core_components.ex index 77dfe6b..0d96ae4 100644 --- a/lib/comfycamp_web/components/core_components.ex +++ b/lib/comfycamp_web/components/core_components.ex @@ -18,6 +18,7 @@ defmodule ComfycampWeb.CoreComponents do alias Phoenix.LiveView.JS import ComfycampWeb.Gettext + import ComfycampWeb.Icons @doc """ Renders a modal. @@ -85,93 +86,6 @@ defmodule ComfycampWeb.CoreComponents do """ end - @doc """ - Renders flash notices. - - ## Examples - - <.flash kind={:info} flash={@flash} /> - <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! - """ - attr :id, :string, doc: "the optional id of flash container" - attr :flash, :map, default: %{}, doc: "the map of flash messages to display" - attr :title, :string, default: nil - attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" - attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" - - slot :inner_block, doc: "the optional inner block that renders the flash message" - - def flash(assigns) do - assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) - - ~H""" -
hide("##{@id}")} - role="alert" - class={[ - "flash", - @kind == :info && "flash-info", - @kind == :error && "flash-error" - ]} - {@rest} - > -

- <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" /> - <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" /> - <%= @title %> -

-

<%= msg %>

- -
- """ - end - - @doc """ - Shows the flash group with standard titles and content. - - ## Examples - - <.flash_group flash={@flash} /> - """ - attr :flash, :map, required: true, doc: "the map of flash messages" - attr :id, :string, default: "flash-group", doc: "the optional id of flash container" - - def flash_group(assigns) do - ~H""" -
- <.flash kind={:info} title={gettext("Success!")} flash={@flash} /> - <.flash kind={:error} title={gettext("Error!")} flash={@flash} /> - <.flash - id="client-error" - kind={:error} - title={gettext("We can't find the internet")} - phx-disconnected={show(".phx-client-error #client-error")} - phx-connected={hide("#client-error")} - hidden - > - <%= gettext("Attempting to reconnect") %> - <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> - - - <.flash - id="server-error" - kind={:error} - title={gettext("Something went wrong!")} - phx-disconnected={show(".phx-server-error #server-error")} - phx-connected={hide("#server-error")} - hidden - > - <%= gettext("Hang in there while we get back on track") %> - <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> - -
- """ - end - @doc """ Renders a simple form. @@ -565,33 +479,6 @@ defmodule ComfycampWeb.CoreComponents do """ end - @doc """ - Renders a [Heroicon](https://heroicons.com). - - Heroicons come in three styles – outline, solid, and mini. - By default, the outline style is used, but solid and mini may - be applied by using the `-solid` and `-mini` suffix. - - You can customize the size and colors of the icons by setting - width, height, and background color classes. - - Icons are extracted from the `deps/heroicons` directory and bundled within - your compiled app.css by the plugin in your `assets/tailwind.config.js`. - - ## Examples - - <.icon name="hero-x-mark-solid" /> - <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> - """ - attr :name, :string, required: true - attr :class, :string, default: nil - - def icon(%{name: "hero-" <> _} = assigns) do - ~H""" - - """ - end - ## JS Commands def show(js \\ %JS{}, selector) do diff --git a/lib/comfycamp_web/components/flash.ex b/lib/comfycamp_web/components/flash.ex new file mode 100644 index 0000000..005778e --- /dev/null +++ b/lib/comfycamp_web/components/flash.ex @@ -0,0 +1,97 @@ +defmodule ComfycampWeb.Flash do + @moduledoc """ + Default flash component. + """ + use Phoenix.Component + alias Phoenix.LiveView.JS + import ComfycampWeb.Gettext + import ComfycampWeb.CoreComponents + import ComfycampWeb.Icons + + @doc """ + Renders flash notices. + + ## Examples + + <.flash kind={:info} flash={@flash} /> + <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! + """ + attr :id, :string, doc: "the optional id of flash container" + attr :flash, :map, default: %{}, doc: "the map of flash messages to display" + attr :title, :string, default: nil + attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" + attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" + + slot :inner_block, doc: "the optional inner block that renders the flash message" + + def flash(assigns) do + assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) + + ~H""" +
hide("##{@id}")} + role="alert" + class={[ + "flash", + @kind == :info && "flash-info", + @kind == :error && "flash-error" + ]} + {@rest} + > +

+ <.icon :if={@kind == :error} name="hero-exclamation-circle" /> + <.icon :if={@kind == :info} name="hero-information-circle" /> + <%= @title %> +

+

<%= msg %>

+ +
+ """ + end + + @doc """ + Shows the flash group with standard titles and content. + + ## Examples + + <.flash_group flash={@flash} /> + """ + attr :flash, :map, required: true, doc: "the map of flash messages" + attr :id, :string, default: "flash-group", doc: "the optional id of flash container" + + def flash_group(assigns) do + ~H""" +
+ <.flash kind={:info} title={gettext("Success!")} flash={@flash} /> + <.flash kind={:error} title={gettext("Error!")} flash={@flash} /> + <.flash + id="client-error" + kind={:error} + title={gettext("We can't find the internet")} + phx-disconnected={show(".phx-client-error #client-error")} + phx-connected={hide("#client-error")} + hidden + > + <%= gettext("Attempting to reconnect") %> + <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> + + + <.flash + id="server-error" + kind={:error} + title={gettext("Something went wrong!")} + phx-disconnected={show(".phx-server-error #server-error")} + phx-connected={hide("#server-error")} + hidden + > + <%= gettext("Hang in there while we get back on track") %> + <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> + +
+ """ + end +end diff --git a/lib/comfycamp_web/components/icons.ex b/lib/comfycamp_web/components/icons.ex new file mode 100644 index 0000000..a2451da --- /dev/null +++ b/lib/comfycamp_web/components/icons.ex @@ -0,0 +1,58 @@ +defmodule ComfycampWeb.Icons do + @moduledoc """ + Provides reusable svg icons. + + I would like to store icons as .svg files, + but I don’t want to write a custom loader. + + When trying to use the img element, we lose the ability to inherit color. + + For now, I find this approach tolerable. + """ + use Phoenix.Component + + @doc """ + Icon component. + + The default icon is face-frown from heroicons. + + ## Examples + + <.icon name="hero-x-mark-solid" /> + <.icon name="hero-arrow-path" class="custom-class" /> + """ + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-exclamation-circle"} = assigns) do + ~H""" + + + + """ + end + + def icon(%{name: "hero-information-circle"} = assigns) do + ~H""" + + + + """ + end + + def icon(%{name: "hero-x-mark"} = assigns) do + ~H""" + + + + """ + end + + def icon(assigns) do + ~H""" + + + + """ + end +end diff --git a/mix.exs b/mix.exs index 7eb214c..0990638 100644 --- a/mix.exs +++ b/mix.exs @@ -42,13 +42,6 @@ defmodule Comfycamp.MixProject do {:floki, ">= 0.30.0", only: :test}, {:phoenix_live_dashboard, "~> 0.8.3"}, {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, - {:heroicons, - github: "tailwindlabs/heroicons", - tag: "v2.1.1", - sparse: "optimized", - app: false, - compile: false, - depth: 1}, {:swoosh, "~> 1.5"}, {:finch, "~> 0.13"}, {:telemetry_metrics, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index a4dad25..92cfadf 100644 --- a/mix.lock +++ b/mix.lock @@ -12,7 +12,6 @@ "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, - "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot new file mode 100644 index 0000000..c6d242f --- /dev/null +++ b/priv/gettext/default.pot @@ -0,0 +1,53 @@ +## This file is a PO Template file. +## +## "msgid"s here are often extracted from source code. +## Add new messages manually only if they're dynamic +## messages 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. +# +msgid "" +msgstr "" + +#: lib/comfycamp_web/components/core_components.ex:390 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:78 +#, elixir-autogen, elixir-format +msgid "Attempting to reconnect" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:69 +#, elixir-autogen, elixir-format +msgid "Error!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:90 +#, elixir-autogen, elixir-format +msgid "Hang in there while we get back on track" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:85 +#, elixir-autogen, elixir-format +msgid "Something went wrong!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:68 +#, elixir-autogen, elixir-format +msgid "Success!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:73 +#, elixir-autogen, elixir-format +msgid "We can't find the internet" +msgstr "" + +#: lib/comfycamp_web/components/core_components.ex:74 +#: lib/comfycamp_web/components/flash.ex:48 +#, elixir-autogen, elixir-format +msgid "close" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po new file mode 100644 index 0000000..4ed7b27 --- /dev/null +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -0,0 +1,53 @@ +## "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" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: lib/comfycamp_web/components/core_components.ex:390 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:78 +#, elixir-autogen, elixir-format +msgid "Attempting to reconnect" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:69 +#, elixir-autogen, elixir-format +msgid "Error!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:90 +#, elixir-autogen, elixir-format +msgid "Hang in there while we get back on track" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:85 +#, elixir-autogen, elixir-format +msgid "Something went wrong!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:68 +#, elixir-autogen, elixir-format +msgid "Success!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:73 +#, elixir-autogen, elixir-format +msgid "We can't find the internet" +msgstr "" + +#: lib/comfycamp_web/components/core_components.ex:74 +#: lib/comfycamp_web/components/flash.ex:48 +#, elixir-autogen, elixir-format +msgid "close" +msgstr "" diff --git a/priv/gettext/ru/LC_MESSAGES/default.po b/priv/gettext/ru/LC_MESSAGES/default.po new file mode 100644 index 0000000..52d0193 --- /dev/null +++ b/priv/gettext/ru/LC_MESSAGES/default.po @@ -0,0 +1,53 @@ +## "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: ru\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100 != 11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n" + +#: lib/comfycamp_web/components/core_components.ex:390 +#, elixir-autogen, elixir-format +msgid "Actions" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:78 +#, elixir-autogen, elixir-format +msgid "Attempting to reconnect" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:69 +#, elixir-autogen, elixir-format +msgid "Error!" +msgstr "Ошибка!" + +#: lib/comfycamp_web/components/flash.ex:90 +#, elixir-autogen, elixir-format +msgid "Hang in there while we get back on track" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:85 +#, elixir-autogen, elixir-format +msgid "Something went wrong!" +msgstr "" + +#: lib/comfycamp_web/components/flash.ex:68 +#, elixir-autogen, elixir-format +msgid "Success!" +msgstr "Успех!" + +#: lib/comfycamp_web/components/flash.ex:73 +#, elixir-autogen, elixir-format +msgid "We can't find the internet" +msgstr "" + +#: lib/comfycamp_web/components/core_components.ex:74 +#: lib/comfycamp_web/components/flash.ex:48 +#, elixir-autogen, elixir-format +msgid "close" +msgstr "" diff --git a/priv/gettext/ru/LC_MESSAGES/errors.po b/priv/gettext/ru/LC_MESSAGES/errors.po new file mode 100644 index 0000000..66e9a13 --- /dev/null +++ b/priv/gettext/ru/LC_MESSAGES/errors.po @@ -0,0 +1,111 @@ +## "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: ru\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100 != 11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n" + +msgid "can't be blank" +msgstr "" + +msgid "has already been taken" +msgstr "" + +msgid "is invalid" +msgstr "" + +msgid "must be accepted" +msgstr "" + +msgid "has invalid format" +msgstr "" + +msgid "has an invalid entry" +msgstr "" + +msgid "is reserved" +msgstr "" + +msgid "does not match confirmation" +msgstr "" + +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should be %{count} byte(s)" +msgid_plural "should be %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should be at least %{count} byte(s)" +msgid_plural "should be at least %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +msgid "should be at most %{count} byte(s)" +msgid_plural "should be at most %{count} byte(s)" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +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 ""