changed
README.md
|
@@ -102,7 +102,7 @@ by adding `kanta` to your list of dependencies in `mix.exs`:
|
102
102
|
```elixir
|
103
103
|
def deps do
|
104
104
|
[
|
105
|
- {:kanta, "~> 0.3.0"},
|
105
|
+ {:kanta, "~> 0.3.1"},
|
106
106
|
{:gettext, git: "[email protected]:ravensiris/gettext.git", branch: "runtime-gettext"}
|
107
107
|
]
|
108
108
|
end
|
|
@@ -180,9 +180,9 @@ In the `application.ex` file of our project, we add Kanta and its configuration
|
180
180
|
|
181
181
|
## Kanta UI
|
182
182
|
|
183
|
- Inside your `router.ex` file we need to connect the Kanta panel using the kanta_dashboard macro.
|
183
|
+ Inside your `router.ex` file we need to connect the Kanta panel using the kanta_dashboard macro.
|
184
184
|
|
185
|
- ```elixir
|
185
|
+ ```elixir
|
186
186
|
import KantaWeb.Router
|
187
187
|
|
188
188
|
scope "/" do
|
|
@@ -226,7 +226,7 @@ Not all of us are polyglots, and sometimes we need the help of machine translati
|
226
226
|
|
227
227
|
```elixir
|
228
228
|
# mix.exs
|
229
|
- defp deps
|
229
|
+ defp deps do
|
230
230
|
...
|
231
231
|
{:kanta_deep_l_plugin, "~> 0.1.1"}
|
232
232
|
end
|
|
@@ -241,6 +241,35 @@ config :kanta,
|
241
241
|
]
|
242
242
|
```
|
243
243
|
|
244
|
+ ## KantaSync
|
245
|
+
|
246
|
+ The [KantaSync plugin](https://github.com/curiosum-dev/kanta_sync_plugin) allows you to synchronize translations between your production and staging/dev environments. It ensures that any changes made to translations in one are reflected in the others, helping you maintain consistency across different stages of development.
|
247
|
+
|
248
|
+ ```elixir
|
249
|
+ # mix.exs
|
250
|
+ defp deps do
|
251
|
+ ...
|
252
|
+ {:kanta_sync_plugin, "~> 0.1.0"}
|
253
|
+ end
|
254
|
+ ```
|
255
|
+
|
256
|
+ You need to have Kanta API configured by using kanta_api macro.
|
257
|
+
|
258
|
+ ```elixir
|
259
|
+ # router.ex
|
260
|
+ import KantaWeb.Router
|
261
|
+
|
262
|
+ scope "/" do
|
263
|
+ kanta_api("/kanta-api")
|
264
|
+ end
|
265
|
+ ```
|
266
|
+
|
267
|
+ ### Authorization
|
268
|
+
|
269
|
+ Set `KANTA_SECRET_TOKEN` environment variable for restricting API access. It should be generated with `mix phx.gen.secret 256` and both environments must have the same `KANTA_SECRET_TOKEN` environment variables.
|
270
|
+
|
271
|
+ You can also disable default authorization mechanism and use your own, by passing `disable_api_authorization: true` option into Kanta's config.
|
272
|
+
|
244
273
|
## PO Writer
|
245
274
|
|
246
275
|
Kanta was created to allow easy management of static text translations in the application, however, for various reasons like wanting a backup or parallel use of other tools like TMS etc. you may want to overwrite .po files with translations entered in Kanta. To install it append `{:kanta_po_writer_plugin, git: "https://github.com/curiosum-dev/kanta_po_writer_plugin"}` to your `deps` list. Currently, it's not on Hex because it's in a pre-release version. Then add `Kanta.Plugins.POWriter` to the list of plugins, and new functions will appear in the Kanta UI to allow writing to .po files.
|
changed
hex_metadata.config
|
@@ -1,6 +1,6 @@
|
1
1
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/curiosum-dev/kanta">>}]}.
|
2
2
|
{<<"name">>,<<"kanta">>}.
|
3
|
- {<<"version">>,<<"0.3.0">>}.
|
3
|
+ {<<"version">>,<<"0.3.1">>}.
|
4
4
|
{<<"description">>,
|
5
5
|
<<"User-friendly translations manager for Elixir/Phoenix projects.">>}.
|
6
6
|
{<<"elixir">>,<<"~> 1.14">>}.
|
|
@@ -13,9 +13,11 @@
|
13
13
|
<<"lib/kanta/migrations/postgresql/v01.ex">>,
|
14
14
|
<<"lib/kanta/migrations/postgresql/v02.ex">>,
|
15
15
|
<<"lib/kanta/migrations/postgresql.ex">>,<<"lib/kanta/translations.ex">>,
|
16
|
- <<"lib/kanta/cache.ex">>,<<"lib/kanta/types.ex">>,
|
17
|
- <<"lib/kanta/validator.ex">>,<<"lib/kanta/config.ex">>,
|
18
|
- <<"lib/kanta/utils">>,<<"lib/kanta/utils/module_utils.ex">>,
|
16
|
+ <<"lib/kanta/cache.ex">>,<<"lib/kanta/types.ex">>,<<"lib/kanta/specs">>,
|
17
|
+ <<"lib/kanta/specs/schemata_spec.ex">>,<<"lib/kanta/validator.ex">>,
|
18
|
+ <<"lib/kanta/config.ex">>,<<"lib/kanta/utils">>,
|
19
|
+ <<"lib/kanta/utils/database_populator.ex">>,
|
20
|
+ <<"lib/kanta/utils/get_schemata.ex">>,<<"lib/kanta/utils/module_utils.ex">>,
|
19
21
|
<<"lib/kanta/query.ex">>,<<"lib/kanta/migration.ex">>,
|
20
22
|
<<"lib/kanta/gettext">>,<<"lib/kanta/gettext/repo.ex">>,
|
21
23
|
<<"lib/kanta/po_files">>,
|
|
@@ -33,6 +35,7 @@
|
33
35
|
<<"lib/kanta/translations/plural_translation/finders">>,
|
34
36
|
<<"lib/kanta/translations/plural_translation/finders/get_plural_translation.ex">>,
|
35
37
|
<<"lib/kanta/translations/plural_translation/finders/list_plural_translations.ex">>,
|
38
|
+ <<"lib/kanta/translations/plural_translation/finders/list_translated_plural_translations.ex">>,
|
36
39
|
<<"lib/kanta/translations/plural_translation/plural_translation_spec.ex">>,
|
37
40
|
<<"lib/kanta/translations/messages">>,
|
38
41
|
<<"lib/kanta/translations/messages/message_spec.ex">>,
|
|
@@ -71,6 +74,7 @@
|
71
74
|
<<"lib/kanta/translations/singular_translation/singular_translations.ex">>,
|
72
75
|
<<"lib/kanta/translations/singular_translation/finders">>,
|
73
76
|
<<"lib/kanta/translations/singular_translation/finders/get_singular_translation.ex">>,
|
77
|
+ <<"lib/kanta/translations/singular_translation/finders/list_translated_singular_translations.ex">>,
|
74
78
|
<<"lib/kanta/translations/singular_translation/finders/list_singular_translations.ex">>,
|
75
79
|
<<"lib/kanta/translations/singular_translation/singular_translation_spec.ex">>,
|
76
80
|
<<"lib/kanta/translations/locale.ex">>,<<"lib/kanta_web">>,
|
|
@@ -92,6 +96,7 @@
|
92
96
|
<<"lib/kanta_web/components/shared/select/select.html.heex">>,
|
93
97
|
<<"lib/kanta_web/components/shared/select/select.ex">>,
|
94
98
|
<<"lib/kanta_web/plugs">>,<<"lib/kanta_web/plugs/redirect.ex">>,
|
99
|
+ <<"lib/kanta_web/plugs/api_auth_plug.ex">>,
|
95
100
|
<<"lib/kanta_web/plugs/assets.ex">>,<<"lib/kanta_web/live">>,
|
96
101
|
<<"lib/kanta_web/live/dashboard">>,
|
97
102
|
<<"lib/kanta_web/live/dashboard/dashboard_live">>,
|
|
@@ -142,6 +147,14 @@
|
142
147
|
<<"lib/kanta_web/live/translations/translation_form_live/components/singular_translation_form/singular_translation_form.html.heex">>,
|
143
148
|
<<"lib/kanta_web/templates">>,<<"lib/kanta_web/templates/layouts">>,
|
144
149
|
<<"lib/kanta_web/templates/layouts/dashboard.html.heex">>,
|
150
|
+ <<"lib/kanta_web/controllers">>,<<"lib/kanta_web/controllers/api">>,
|
151
|
+ <<"lib/kanta_web/controllers/api/kanta_api_controller.ex">>,
|
152
|
+ <<"lib/kanta_web/controllers/api/plural_translations_controller.ex">>,
|
153
|
+ <<"lib/kanta_web/controllers/api/contexts_controller.ex">>,
|
154
|
+ <<"lib/kanta_web/controllers/api/domains_controller.ex">>,
|
155
|
+ <<"lib/kanta_web/controllers/api/singular_translations_controller.ex">>,
|
156
|
+ <<"lib/kanta_web/controllers/api/locales_controller.ex">>,
|
157
|
+ <<"lib/kanta_web/controllers/api/messages_controller.ex">>,
|
145
158
|
<<"lib/kanta_web/views">>,<<"lib/kanta_web/views/layout_view.ex">>,
|
146
159
|
<<"lib/kanta.ex">>,<<"priv">>,<<"priv/iso639.json">>,<<"dist">>,
|
147
160
|
<<"dist/css">>,<<"dist/css/app.css">>,<<"dist/js">>,<<"dist/js/app.js">>,
|
changed
lib/kanta/config.ex
|
@@ -7,14 +7,16 @@ defmodule Kanta.Config do
|
7
7
|
otp_name: atom(),
|
8
8
|
repo: module(),
|
9
9
|
endpoint: module(),
|
10
|
- plugins: false | [module() | {module() | Keyword.t()}]
|
10
|
+ plugins: false | [module() | {module() | Keyword.t()}],
|
11
|
+ disable_api_authorization: boolean()
|
11
12
|
}
|
12
13
|
|
13
14
|
defstruct name: Kanta,
|
14
15
|
otp_name: nil,
|
15
16
|
repo: nil,
|
16
17
|
endpoint: nil,
|
17
|
- plugins: []
|
18
|
+ plugins: [],
|
19
|
+ disable_api_authorization: false
|
18
20
|
|
19
21
|
alias Kanta.Validator
|
20
22
|
|
|
@@ -71,6 +73,15 @@ defmodule Kanta.Config do
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
76
|
+ defp validate_opt(_opts, {:disable_api_authorization, disable_api_authorization}) do
|
77
|
+ if is_boolean(disable_api_authorization) do
|
78
|
+ :ok
|
79
|
+ else
|
80
|
+ {:error,
|
81
|
+ "expected :disable_api_authorization to be a boolean, got: #{inspect(disable_api_authorization)}"}
|
82
|
+ end
|
83
|
+ end
|
84
|
+
|
74
85
|
defp validate_opt(_opts, option) do
|
75
86
|
{:unknown, option, __MODULE__}
|
76
87
|
end
|
changed
lib/kanta/query.ex
|
@@ -34,10 +34,21 @@ defmodule Kanta.Query do
|
34
34
|
Repo.get_repo().one(query, opts)
|
35
35
|
end
|
36
36
|
|
37
|
- def paginate(query, page \\ 1, per_page \\ 15)
|
38
|
- def paginate(query, nil, nil), do: paginate(query, 1, 15)
|
37
|
+ @default_page_size 100
|
38
|
+ @minimum_per_page 10
|
39
|
+
|
40
|
+ @spec paginate(Ecto.Query.t(), integer() | nil, integer() | nil) :: map()
|
41
|
+ @spec paginate(Ecto.Query.t(), integer() | nil) :: map()
|
42
|
+ @spec paginate(Ecto.Query.t()) :: map()
|
43
|
+
|
44
|
+ def paginate(query, page \\ 1, per_page \\ @default_page_size)
|
39
45
|
|
40
46
|
def paginate(query, page, per_page) do
|
47
|
+ page = if is_number(page), do: max(page, 1), else: 1
|
48
|
+
|
49
|
+ per_page =
|
50
|
+ if is_number(per_page), do: max(per_page, @minimum_per_page), else: @default_page_size
|
51
|
+
|
41
52
|
%{
|
42
53
|
entries: entries,
|
43
54
|
page_number: page_number,
|
|
@@ -51,7 +62,7 @@ defmodule Kanta.Query do
|
51
62
|
caller: self(),
|
52
63
|
module: Repo.get_repo(),
|
53
64
|
page_number: page || 1,
|
54
|
- page_size: per_page || 15,
|
65
|
+ page_size: per_page || @default_page_size,
|
55
66
|
options: []
|
56
67
|
}
|
57
68
|
)
|
added
lib/kanta/specs/schemata_spec.ex
|
@@ -0,0 +1,7 @@
|
1
|
+ defmodule Kanta.Specs.SchemataSpec do
|
2
|
+ @moduledoc false
|
3
|
+
|
4
|
+ @type schema() :: {String.t(), %{schema: atom(), conflict_target: atom() | [atom()]}}
|
5
|
+
|
6
|
+ @type t() :: [schema()]
|
7
|
+ end
|
changed
lib/kanta/translations/context.ex
|
@@ -8,8 +8,13 @@ defmodule Kanta.Translations.Context do
|
8
8
|
|
9
9
|
alias Kanta.Translations.Message
|
10
10
|
|
11
|
+ @required_fields ~w(name)a
|
12
|
+ @optional_fields ~w(description color)a
|
13
|
+
|
11
14
|
@type t() :: Kanta.Translations.ContextSpec.t()
|
12
15
|
|
16
|
+ @derive {Jason.Encoder, only: [:id] ++ @required_fields ++ @optional_fields}
|
17
|
+
|
13
18
|
schema "kanta_contexts" do
|
14
19
|
field :name, :string
|
15
20
|
field :description, :string
|
|
@@ -22,7 +27,7 @@ defmodule Kanta.Translations.Context do
|
22
27
|
|
23
28
|
def changeset(struct, params) do
|
24
29
|
struct
|
25
|
- |> cast(params, [:name, :description, :color])
|
26
|
- |> validate_required([:name])
|
30
|
+ |> cast(params, @required_fields ++ @optional_fields)
|
31
|
+ |> validate_required(@required_fields)
|
27
32
|
end
|
28
33
|
end
|
changed
lib/kanta/translations/domain.ex
|
@@ -8,8 +8,13 @@ defmodule Kanta.Translations.Domain do
|
8
8
|
|
9
9
|
alias Kanta.Translations.Message
|
10
10
|
|
11
|
+ @required_fields ~w(name)a
|
12
|
+ @optional_fields ~w(description color)a
|
13
|
+
|
11
14
|
@type t() :: Kanta.Translations.DomainSpec.t()
|
12
15
|
|
16
|
+ @derive {Jason.Encoder, only: [:id] ++ @required_fields ++ @optional_fields}
|
17
|
+
|
13
18
|
schema "kanta_domains" do
|
14
19
|
field :name, :string
|
15
20
|
field :description, :string
|
|
@@ -22,7 +27,7 @@ defmodule Kanta.Translations.Domain do
|
22
27
|
|
23
28
|
def changeset(struct, params) do
|
24
29
|
struct
|
25
|
- |> cast(params, [:name, :description, :color])
|
26
|
- |> validate_required([:name])
|
30
|
+ |> cast(params, @required_fields ++ @optional_fields)
|
31
|
+ |> validate_required(@required_fields)
|
27
32
|
end
|
28
33
|
end
|
changed
lib/kanta/translations/locale.ex
|
@@ -7,11 +7,13 @@ defmodule Kanta.Translations.Locale do
|
7
7
|
import Ecto.Changeset
|
8
8
|
alias Kanta.Translations.SingularTranslation
|
9
9
|
|
10
|
- @all_fields ~w(iso639_code name plurals_header native_name family wiki_url colors)a
|
11
10
|
@required_fields ~w(iso639_code name native_name)a
|
11
|
+ @optional_fields ~w(plurals_header family wiki_url colors)a
|
12
12
|
|
13
13
|
@type t() :: Kanta.Translations.LocaleSpec.t()
|
14
14
|
|
15
|
+ @derive {Jason.Encoder, only: [:id] ++ @required_fields ++ @optional_fields}
|
16
|
+
|
15
17
|
schema "kanta_locales" do
|
16
18
|
field :iso639_code, :string
|
17
19
|
field :name, :string
|
|
@@ -29,7 +31,7 @@ defmodule Kanta.Translations.Locale do
|
29
31
|
|
30
32
|
def changeset(struct, params) do
|
31
33
|
struct
|
32
|
- |> cast(params, @all_fields)
|
34
|
+ |> cast(params, @required_fields ++ @optional_fields)
|
33
35
|
|> validate_required(@required_fields)
|
34
36
|
end
|
35
37
|
end
|
changed
lib/kanta/translations/locale/finders/list_locales.ex
|
@@ -9,6 +9,7 @@ defmodule Kanta.Translations.Locale.Finders.ListLocales do
|
9
9
|
|
10
10
|
def find(params \\ []) do
|
11
11
|
base()
|
12
|
+ |> order_by(:id)
|
12
13
|
|> filter_query(params[:filter])
|
13
14
|
|> preload_resources(params[:preloads] || [])
|
14
15
|
|> paginate(params[:page], params[:per_page])
|
changed
lib/kanta/translations/message.ex
|
@@ -8,10 +8,12 @@ defmodule Kanta.Translations.Message do
|
8
8
|
|
9
9
|
alias Kanta.Translations.{Context, Domain, PluralTranslation, SingularTranslation}
|
10
10
|
|
11
|
+ @required_fields ~w(msgid message_type)a
|
12
|
+ @optional_fields ~w(domain_id context_id)a
|
13
|
+
|
11
14
|
@type t() :: Kanta.Translations.MessageSpec.t()
|
12
15
|
|
13
|
- @all_fields ~w(msgid message_type domain_id context_id)a
|
14
|
- @required_fields ~w(msgid message_type)a
|
16
|
+ @derive {Jason.Encoder, only: [:id] ++ @required_fields ++ @optional_fields}
|
15
17
|
|
16
18
|
schema "kanta_messages" do
|
17
19
|
field :msgid, :string
|
|
@@ -28,7 +30,7 @@ defmodule Kanta.Translations.Message do
|
28
30
|
|
29
31
|
def changeset(struct, params) do
|
30
32
|
struct
|
31
|
- |> cast(params, @all_fields)
|
33
|
+ |> cast(params, @required_fields ++ @optional_fields)
|
32
34
|
|> validate_required(@required_fields)
|
33
35
|
end
|
34
36
|
end
|
changed
lib/kanta/translations/messages/finders/list_messages.ex
|
@@ -13,13 +13,16 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do
|
13
13
|
@available_filters ~w(domain_id context_id)
|
14
14
|
|
15
15
|
def find(params \\ []) do
|
16
|
+ filters = params[:filter] || %{}
|
17
|
+ query_filters = Map.take(filters, @available_filters)
|
18
|
+
|
16
19
|
base()
|
17
|
- |> filter_query(Map.take(params[:filter], @available_filters))
|
18
|
- |> not_translated_query(params[:filter])
|
19
|
- |> search_subquery([locale_id: params[:filter]["locale_id"]], params[:search])
|
20
|
+ |> filter_query(query_filters)
|
21
|
+ |> not_translated_query(filters)
|
22
|
+ |> search_subquery(filters, params[:search])
|
20
23
|
|> distinct(true)
|
21
24
|
|> preload_resources(params[:preloads] || [])
|
22
|
- |> paginate(String.to_integer(params[:page] || "1"), params[:per_page])
|
25
|
+ |> paginate(params[:page], params[:per_page])
|
23
26
|
end
|
24
27
|
|
25
28
|
defp not_translated_query(query, %{"locale_id" => locale_id, "not_translated" => "true"}) do
|
|
@@ -41,6 +44,12 @@ defmodule Kanta.Translations.Messages.Finders.ListMessages do
|
41
44
|
defp search_subquery(query, _, nil), do: query
|
42
45
|
defp search_subquery(query, _, ""), do: query
|
43
46
|
|
47
|
+ defp search_subquery(query, %{"locale_id" => locale_id}, search) do
|
48
|
+ search_subquery(query, [locale_id: locale_id], search)
|
49
|
+ end
|
50
|
+
|
51
|
+ defp search_subquery(query, filter, _) when is_map(filter), do: query
|
52
|
+
|
44
53
|
defp search_subquery(query, filter, search) do
|
45
54
|
sub =
|
46
55
|
base()
|
changed
lib/kanta/translations/plural_translation.ex
|
@@ -8,11 +8,13 @@ defmodule Kanta.Translations.PluralTranslation do
|
8
8
|
|
9
9
|
alias Kanta.Translations.{Locale, Message}
|
10
10
|
|
11
|
- @all_fields ~w(nplural_index original_text translated_text locale_id message_id)a
|
12
11
|
@required_fields ~w(nplural_index message_id locale_id)a
|
12
|
+ @optional_fields ~w(original_text translated_text)a
|
13
13
|
|
14
14
|
@type t() :: Kanta.Translations.PluralTranslationSpec.t()
|
15
15
|
|
16
|
+ @derive {Jason.Encoder, only: [:id] ++ @required_fields ++ @optional_fields}
|
17
|
+
|
16
18
|
schema "kanta_plural_translations" do
|
17
19
|
field :nplural_index, :integer
|
18
20
|
field :original_text, :string
|
|
@@ -26,7 +28,7 @@ defmodule Kanta.Translations.PluralTranslation do
|
26
28
|
|
27
29
|
def changeset(struct, attrs \\ %{}) do
|
28
30
|
struct
|
29
|
- |> cast(attrs, @all_fields)
|
31
|
+ |> cast(attrs, @required_fields ++ @optional_fields)
|
30
32
|
|> validate_required(@required_fields)
|
31
33
|
|> foreign_key_constraint(:locale_id)
|
32
34
|
|> foreign_key_constraint(:message_id)
|
added
lib/kanta/translations/plural_translation/finders/list_translated_plural_translations.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule Kanta.Translations.PluralTranslations.Finders.ListTranslatedPluralTranslations do
|
2
|
+ @moduledoc """
|
3
|
+ Query module aka Finder responsible for listing translated plural translations
|
4
|
+ """
|
5
|
+
|
6
|
+ use Kanta.Query,
|
7
|
+ module: Kanta.Translations.PluralTranslation,
|
8
|
+ binding: :plural_translation
|
9
|
+
|
10
|
+ alias Kanta.Repo
|
11
|
+
|
12
|
+ def find do
|
13
|
+ base()
|
14
|
+ |> translated_query()
|
15
|
+ |> Repo.get_repo().all()
|
16
|
+ end
|
17
|
+
|
18
|
+ defp translated_query(query) do
|
19
|
+ from(pt in query,
|
20
|
+ where: not is_nil(pt.translated_text) and pt.translated_text != ""
|
21
|
+ )
|
22
|
+ end
|
23
|
+ end
|
changed
lib/kanta/translations/singular_translation.ex
|
@@ -7,11 +7,13 @@ defmodule Kanta.Translations.SingularTranslation do
|
7
7
|
import Ecto.Changeset
|
8
8
|
alias Kanta.Translations.{Locale, Message}
|
9
9
|
|
10
|
- @all_fields ~w(original_text translated_text locale_id message_id)a
|
11
10
|
@required_fields ~w(message_id locale_id)a
|
11
|
+ @optional_fields ~w(original_text translated_text)a
|
12
12
|
|
13
13
|
@type t() :: Kanta.Translations.SingularTranslationSpec.t()
|
14
14
|
|
15
|
+ @derive {Jason.Encoder, only: [:id] ++ @required_fields ++ @optional_fields}
|
16
|
+
|
15
17
|
schema "kanta_singular_translations" do
|
16
18
|
field :original_text, :string
|
17
19
|
field :translated_text, :string
|
|
@@ -24,7 +26,7 @@ defmodule Kanta.Translations.SingularTranslation do
|
24
26
|
|
25
27
|
def changeset(struct, attrs \\ %{}) do
|
26
28
|
struct
|
27
|
- |> cast(attrs, @all_fields)
|
29
|
+ |> cast(attrs, @required_fields ++ @optional_fields)
|
28
30
|
|> validate_required(@required_fields)
|
29
31
|
|> foreign_key_constraint(:locale_id)
|
30
32
|
|> foreign_key_constraint(:message_id)
|
added
lib/kanta/translations/singular_translation/finders/list_translated_singular_translations.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule Kanta.Translations.SingularTranslations.Finders.ListTranslatedSingularTranslations do
|
2
|
+ @moduledoc """
|
3
|
+ Query module aka Finder responsible for listing translated singular translations
|
4
|
+ """
|
5
|
+
|
6
|
+ use Kanta.Query,
|
7
|
+ module: Kanta.Translations.SingularTranslation,
|
8
|
+ binding: :singular_translation
|
9
|
+
|
10
|
+ alias Kanta.Repo
|
11
|
+
|
12
|
+ def find do
|
13
|
+ base()
|
14
|
+ |> translated_query()
|
15
|
+ |> Repo.get_repo().all()
|
16
|
+ end
|
17
|
+
|
18
|
+ defp translated_query(query) do
|
19
|
+ from(st in query,
|
20
|
+ where: not is_nil(st.translated_text) and st.translated_text != ""
|
21
|
+ )
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta/utils/database_populator.ex
|
@@ -0,0 +1,43 @@
|
1
|
+ defmodule Kanta.Utils.DatabasePopulator do
|
2
|
+ @moduledoc false
|
3
|
+
|
4
|
+ import Ecto.Changeset
|
5
|
+
|
6
|
+ alias Kanta.Repo
|
7
|
+ alias Kanta.Utils.GetSchemata
|
8
|
+
|
9
|
+ @resource_name_to_schema GetSchemata.call()
|
10
|
+ |> Map.new()
|
11
|
+
|
12
|
+ @spec call(atom(), String.t(), [map()]) :: no_return()
|
13
|
+ def call(repo \\ Repo.get_repo(), resource_name, entries) do
|
14
|
+ schema = @resource_name_to_schema[resource_name]
|
15
|
+
|
16
|
+ entries
|
17
|
+ |> Enum.each(&populate(repo, schema, &1))
|
18
|
+ end
|
19
|
+
|
20
|
+ defp populate(repo, %{schema: schema, conflict_target: conflict_target}, entry) do
|
21
|
+ schema
|
22
|
+ |> struct()
|
23
|
+ |> change(entry |> keys_to_atoms())
|
24
|
+ |> repo.insert!(on_conflict: :replace_all, conflict_target: conflict_target)
|
25
|
+ end
|
26
|
+
|
27
|
+ defp keys_to_atoms(map) do
|
28
|
+ Map.new(map, &reduce_keys_to_atoms/1)
|
29
|
+ end
|
30
|
+
|
31
|
+ defp reduce_keys_to_atoms({"message_type", "singular"}) do
|
32
|
+ {:message_type, :singular}
|
33
|
+ end
|
34
|
+
|
35
|
+ defp reduce_keys_to_atoms({"message_type", "plural"}) do
|
36
|
+ {:message_type, :plural}
|
37
|
+ end
|
38
|
+
|
39
|
+ defp reduce_keys_to_atoms({key, val}) when is_map(val),
|
40
|
+ do: {String.to_existing_atom(key), keys_to_atoms(val)}
|
41
|
+
|
42
|
+ defp reduce_keys_to_atoms({key, val}), do: {String.to_existing_atom(key), val}
|
43
|
+ end
|
added
lib/kanta/utils/get_schemata.ex
|
@@ -0,0 +1,28 @@
|
1
|
+ defmodule Kanta.Utils.GetSchemata do
|
2
|
+ @moduledoc false
|
3
|
+
|
4
|
+ alias Kanta.Specs.SchemataSpec
|
5
|
+
|
6
|
+ alias Kanta.Translations.{
|
7
|
+ Context,
|
8
|
+ Domain,
|
9
|
+ Locale,
|
10
|
+ Message,
|
11
|
+ PluralTranslation,
|
12
|
+ SingularTranslation
|
13
|
+ }
|
14
|
+
|
15
|
+ @schemata [
|
16
|
+ {"contexts", %{schema: Context, conflict_target: [:name]}},
|
17
|
+ {"domains", %{schema: Domain, conflict_target: [:name]}},
|
18
|
+ {"locales", %{schema: Locale, conflict_target: [:iso639_code]}},
|
19
|
+ {"messages", %{schema: Message, conflict_target: [:id]}},
|
20
|
+ {"singular_translations", %{schema: SingularTranslation, conflict_target: [:id]}},
|
21
|
+ {"plural_translations", %{schema: PluralTranslation, conflict_target: [:id]}}
|
22
|
+ ]
|
23
|
+
|
24
|
+ @spec call :: SchemataSpec.t()
|
25
|
+ def call do
|
26
|
+ @schemata
|
27
|
+ end
|
28
|
+ end
|
added
lib/kanta_web/controllers/api/contexts_controller.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule KantaWeb.Api.ContextsController do
|
2
|
+ @moduledoc false
|
3
|
+ use KantaWeb, :controller
|
4
|
+
|
5
|
+ alias Kanta.Translations.Contexts.Finders.ListContexts
|
6
|
+ alias Kanta.Utils.DatabasePopulator
|
7
|
+
|
8
|
+ def index(conn, params) do
|
9
|
+ page = params |> Map.get("page", "1") |> String.to_integer()
|
10
|
+
|
11
|
+ conn
|
12
|
+ |> put_status(200)
|
13
|
+ |> json(ListContexts.find(page: page))
|
14
|
+ end
|
15
|
+
|
16
|
+ def update(conn, %{"entries" => entries}) do
|
17
|
+ DatabasePopulator.call("contexts", entries)
|
18
|
+
|
19
|
+ conn
|
20
|
+ |> put_status(200)
|
21
|
+ |> json(%{status: "OK"})
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta_web/controllers/api/domains_controller.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule KantaWeb.Api.DomainsController do
|
2
|
+ @moduledoc false
|
3
|
+ use KantaWeb, :controller
|
4
|
+
|
5
|
+ alias Kanta.Translations.Domains.Finders.ListDomains
|
6
|
+ alias Kanta.Utils.DatabasePopulator
|
7
|
+
|
8
|
+ def index(conn, params) do
|
9
|
+ page = params |> Map.get("page", "1") |> String.to_integer()
|
10
|
+
|
11
|
+ conn
|
12
|
+ |> put_status(200)
|
13
|
+ |> json(ListDomains.find(page: page))
|
14
|
+ end
|
15
|
+
|
16
|
+ def update(conn, %{"entries" => entries}) do
|
17
|
+ DatabasePopulator.call("domains", entries)
|
18
|
+
|
19
|
+ conn
|
20
|
+ |> put_status(200)
|
21
|
+ |> json(%{status: "OK"})
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta_web/controllers/api/kanta_api_controller.ex
|
@@ -0,0 +1,9 @@
|
1
|
+ defmodule KantaWeb.Api.KantaApiController do
|
2
|
+ use KantaWeb, :controller
|
3
|
+
|
4
|
+ def index(conn, _params) do
|
5
|
+ conn
|
6
|
+ |> put_status(200)
|
7
|
+ |> json(%{status: "OK"})
|
8
|
+ end
|
9
|
+ end
|
added
lib/kanta_web/controllers/api/locales_controller.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule KantaWeb.Api.LocalesController do
|
2
|
+ @moduledoc false
|
3
|
+ use KantaWeb, :controller
|
4
|
+
|
5
|
+ alias Kanta.Translations.Locale.Finders.ListLocales
|
6
|
+ alias Kanta.Utils.DatabasePopulator
|
7
|
+
|
8
|
+ def index(conn, params) do
|
9
|
+ page = params |> Map.get("page", "1") |> String.to_integer()
|
10
|
+
|
11
|
+ conn
|
12
|
+ |> put_status(200)
|
13
|
+ |> json(ListLocales.find(page: page))
|
14
|
+ end
|
15
|
+
|
16
|
+ def update(conn, %{"entries" => entries}) do
|
17
|
+ DatabasePopulator.call("locales", entries)
|
18
|
+
|
19
|
+ conn
|
20
|
+ |> put_status(200)
|
21
|
+ |> json(%{status: "OK"})
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta_web/controllers/api/messages_controller.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule KantaWeb.Api.MessagesController do
|
2
|
+ @moduledoc false
|
3
|
+ use KantaWeb, :controller
|
4
|
+
|
5
|
+ alias Kanta.Translations.Messages.Finders.ListMessages
|
6
|
+ alias Kanta.Utils.DatabasePopulator
|
7
|
+
|
8
|
+ def index(conn, params) do
|
9
|
+ page = params |> Map.get("page", "1") |> String.to_integer()
|
10
|
+
|
11
|
+ conn
|
12
|
+ |> put_status(200)
|
13
|
+ |> json(ListMessages.find(page: page))
|
14
|
+ end
|
15
|
+
|
16
|
+ def update(conn, %{"entries" => entries}) do
|
17
|
+ DatabasePopulator.call("messages", entries)
|
18
|
+
|
19
|
+ conn
|
20
|
+ |> put_status(200)
|
21
|
+ |> json(%{status: "OK"})
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta_web/controllers/api/plural_translations_controller.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule KantaWeb.Api.PluralTranslationsController do
|
2
|
+ @moduledoc false
|
3
|
+ use KantaWeb, :controller
|
4
|
+
|
5
|
+ alias Kanta.Translations.PluralTranslations.Finders.ListPluralTranslations
|
6
|
+ alias Kanta.Utils.DatabasePopulator
|
7
|
+
|
8
|
+ def index(conn, params) do
|
9
|
+ page = params |> Map.get("page", "1") |> String.to_integer()
|
10
|
+
|
11
|
+ conn
|
12
|
+ |> put_status(200)
|
13
|
+ |> json(ListPluralTranslations.find(page: page))
|
14
|
+ end
|
15
|
+
|
16
|
+ def update(conn, %{"entries" => entries}) do
|
17
|
+ DatabasePopulator.call("plural_translations", entries)
|
18
|
+
|
19
|
+ conn
|
20
|
+ |> put_status(200)
|
21
|
+ |> json(%{status: "OK"})
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta_web/controllers/api/singular_translations_controller.ex
|
@@ -0,0 +1,23 @@
|
1
|
+ defmodule KantaWeb.Api.SingularTranslationsController do
|
2
|
+ @moduledoc false
|
3
|
+ use KantaWeb, :controller
|
4
|
+
|
5
|
+ alias Kanta.Translations.SingularTranslations.Finders.ListSingularTranslations
|
6
|
+ alias Kanta.Utils.DatabasePopulator
|
7
|
+
|
8
|
+ def index(conn, params) do
|
9
|
+ page = params |> Map.get("page", "1") |> String.to_integer()
|
10
|
+
|
11
|
+ conn
|
12
|
+ |> put_status(200)
|
13
|
+ |> json(ListSingularTranslations.find(page: page))
|
14
|
+ end
|
15
|
+
|
16
|
+ def update(conn, %{"entries" => entries}) do
|
17
|
+ DatabasePopulator.call("singular_translations", entries)
|
18
|
+
|
19
|
+ conn
|
20
|
+ |> put_status(200)
|
21
|
+ |> json(%{status: "OK"})
|
22
|
+ end
|
23
|
+ end
|
added
lib/kanta_web/plugs/api_auth_plug.ex
|
@@ -0,0 +1,62 @@
|
1
|
+ defmodule KantaWeb.APIAuthPlug do
|
2
|
+ @moduledoc false
|
3
|
+
|
4
|
+ import Plug.Conn
|
5
|
+
|
6
|
+ @kanta_secret_token "KANTA_SECRET_TOKEN"
|
7
|
+
|
8
|
+ def init(_opts), do: %{}
|
9
|
+
|
10
|
+ def call(conn, _opts) do
|
11
|
+ if api_authorization_disabled?() or is_bearer_token_valid?(conn) do
|
12
|
+ conn
|
13
|
+ else
|
14
|
+ conn
|
15
|
+ |> send_resp(
|
16
|
+ 401,
|
17
|
+ "Incorrect authorization Bearer token."
|
18
|
+ )
|
19
|
+ |> halt()
|
20
|
+ end
|
21
|
+ end
|
22
|
+
|
23
|
+ defp is_bearer_token_valid?(conn) do
|
24
|
+ with {:ok, token} <- extract_bearer_token(conn),
|
25
|
+ true <- is_secret_token_matching?(token) do
|
26
|
+ true
|
27
|
+ else
|
28
|
+ _ -> false
|
29
|
+ end
|
30
|
+ end
|
31
|
+
|
32
|
+ defp is_secret_token_matching?(token) do
|
33
|
+ secret_token_env =
|
34
|
+ @kanta_secret_token
|
35
|
+ |> System.get_env()
|
36
|
+
|
37
|
+ if is_nil(secret_token_env) do
|
38
|
+ false
|
39
|
+ else
|
40
|
+ sha256(secret_token_env) == token
|
41
|
+ end
|
42
|
+ end
|
43
|
+
|
44
|
+ defp extract_bearer_token(conn) do
|
45
|
+ case get_req_header(conn, "authorization") do
|
46
|
+ ["Bearer " <> token] ->
|
47
|
+ {:ok, token}
|
48
|
+
|
49
|
+ _ ->
|
50
|
+ :error
|
51
|
+ end
|
52
|
+ end
|
53
|
+
|
54
|
+ defp sha256(token) do
|
55
|
+ :crypto.hash(:sha256, token)
|
56
|
+ |> Base.encode64()
|
57
|
+ end
|
58
|
+
|
59
|
+ defp api_authorization_disabled? do
|
60
|
+ Kanta.config().disable_api_authorization
|
61
|
+ end
|
62
|
+ end
|
changed
lib/kanta_web/router.ex
|
@@ -4,7 +4,7 @@ defmodule KantaWeb.Router do
|
4
4
|
# deps/phoenix/lib/phoenix/router.ex:2:no_return Function call/2 has no local return.
|
5
5
|
@dialyzer {:no_return, {:call, 2}}
|
6
6
|
|
7
|
- defmacro kanta_dashboard(path, opts \\ []) do
|
7
|
+ defmacro kanta_dashboard(path \\ "/kanta", opts \\ []) do
|
8
8
|
opts =
|
9
9
|
if Macro.quoted_literal?(opts) do
|
10
10
|
Macro.prewalk(opts, &expand_alias(&1, __CALLER__))
|
|
@@ -71,6 +71,32 @@ defmodule KantaWeb.Router do
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+ defmacro kanta_api(path \\ "/kanta-api") do
|
75
|
+ quote bind_quoted: binding() do
|
76
|
+ pipeline :kanta_api_pipeline do
|
77
|
+ plug :accepts, ["json"]
|
78
|
+ plug KantaWeb.APIAuthPlug
|
79
|
+ end
|
80
|
+
|
81
|
+ scope path, alias: false, as: false do
|
82
|
+ scope "/", KantaWeb.Api do
|
83
|
+ pipe_through :kanta_api_pipeline
|
84
|
+ get "/", KantaApiController, :index
|
85
|
+
|
86
|
+ resources "/contexts", ContextsController, only: [:index, :update]
|
87
|
+ resources "/domains", DomainsController, only: [:index, :update]
|
88
|
+ resources "/locales", LocalesController, only: [:index, :update]
|
89
|
+ resources "/messages", MessagesController, only: [:index, :update]
|
90
|
+
|
91
|
+ resources "/singular_translations", SingularTranslationsController,
|
92
|
+ only: [:index, :update]
|
93
|
+
|
94
|
+ resources "/plural_translations", PluralTranslationsController, only: [:index, :update]
|
95
|
+ end
|
96
|
+ end
|
97
|
+ end
|
98
|
+ end
|
99
|
+
|
74
100
|
defp expand_alias({:__aliases__, _, _} = alias, env),
|
75
101
|
do: Macro.expand(alias, %{env | function: {:kanta_dashboard, 2}})
|
changed
mix.exs
|
@@ -6,7 +6,7 @@ defmodule Kanta.MixProject do
|
6
6
|
app: :kanta,
|
7
7
|
description: "User-friendly translations manager for Elixir/Phoenix projects.",
|
8
8
|
package: package(),
|
9
|
- version: "0.3.0",
|
9
|
+ version: "0.3.1",
|
10
10
|
elixir: "~> 1.14",
|
11
11
|
elixirc_options: [
|
12
12
|
warnings_as_errors: true
|