mirror of
https://github.com/mirego/accent.git
synced 2024-09-11 10:55:28 +03:00
Add machine translations on project sync
This commit is contained in:
parent
00e21d7510
commit
6bc3274f42
@ -9,6 +9,5 @@ export default class DocumentExportFormatter {
|
||||
chalk.bold.white(documentPath),
|
||||
chalk.gray.dim.underline(path)
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,10 @@ export default class ProjectAddTranslationsFormatter {
|
||||
.map(fetchFromRevision)
|
||||
.join(', ');
|
||||
|
||||
const title = `Adding translations paths (${languages})`;
|
||||
|
||||
console.log(chalk.gray.dim('⎯'.repeat(title.length - 1)));
|
||||
console.log(chalk.magenta(title));
|
||||
|
||||
console.log('');
|
||||
console.log(
|
||||
chalk.white.bold('Adding translations paths →'),
|
||||
chalk.white(languages),
|
||||
chalk.green('✓')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,7 @@ export default class ProjectExportFormatter {
|
||||
log() {
|
||||
const title = 'Writing files locally';
|
||||
|
||||
console.log(chalk.gray.dim('⎯'.repeat(title.length - 1)));
|
||||
console.log(chalk.magenta(title));
|
||||
|
||||
console.log('');
|
||||
console.log(chalk.magenta(title));
|
||||
}
|
||||
}
|
||||
|
@ -14,17 +14,17 @@ export default class ProjectSyncFormatter extends Base {
|
||||
if (flags.version) logFlags.push(chalk.gray(`${flags.version}`));
|
||||
|
||||
console.log(
|
||||
chalk.magenta(
|
||||
'Syncing sources',
|
||||
`(${fetchFromRevision(project.masterRevision)})`,
|
||||
logFlags.join('')
|
||||
)
|
||||
chalk.white.bold('Syncing sources →'),
|
||||
chalk.white(
|
||||
`${fetchFromRevision(project.masterRevision)}`,
|
||||
logFlags.join('') || null
|
||||
),
|
||||
chalk.green('✓')
|
||||
);
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
footerDryRun(time: number) {
|
||||
console.log('');
|
||||
console.log(
|
||||
chalk.gray.dim(
|
||||
'For more informations on operations: https://www.accent.reviews/guides/glossary.html#sync'
|
||||
@ -38,6 +38,7 @@ export default class ProjectSyncFormatter extends Base {
|
||||
}
|
||||
|
||||
footer(time: number) {
|
||||
console.log('');
|
||||
console.log(
|
||||
chalk.gray.dim(
|
||||
'For more informations on operations: https://www.accent.reviews/guides/glossary.html#sync'
|
||||
|
@ -10,6 +10,7 @@ defmodule Accent.MachineTranslationsConfigManager do
|
||||
"config" => %{
|
||||
"key" => params[:config_key]
|
||||
},
|
||||
"enabled_actions" => params[:enabled_actions],
|
||||
"provider" => params[:provider],
|
||||
"use_platform" => params[:use_platform]
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ defmodule Accent.Operation do
|
||||
field(:rollbacked, :boolean, default: false)
|
||||
field(:stats, {:array, :map}, default: [])
|
||||
field(:options, {:array, :string}, default: [])
|
||||
field(:machine_translated, :boolean, default: false)
|
||||
|
||||
embeds_one(:previous_translation, Accent.PreviousTranslation)
|
||||
|
||||
@ -26,7 +27,6 @@ defmodule Accent.Operation do
|
||||
belongs_to(:version, Accent.Version)
|
||||
belongs_to(:translation, Accent.Translation)
|
||||
belongs_to(:project, Accent.Project)
|
||||
belongs_to(:comment, Accent.Comment)
|
||||
belongs_to(:user, Accent.User)
|
||||
belongs_to(:batch_operation, Accent.Operation)
|
||||
belongs_to(:rollbacked_operation, Accent.Operation)
|
||||
@ -37,4 +37,22 @@ defmodule Accent.Operation do
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec to_langue_entry(map(), boolean(), String.t()) :: Langue.Entry.t()
|
||||
def to_langue_entry(operation, is_master, language_slug) do
|
||||
%Langue.Entry{
|
||||
id: operation.key,
|
||||
key: operation.key,
|
||||
value: operation.text,
|
||||
master_value: operation.text,
|
||||
is_master: is_master,
|
||||
comment: operation.file_comment,
|
||||
index: operation.file_index,
|
||||
value_type: operation.value_type,
|
||||
locked: operation.locked,
|
||||
plural: operation.plural,
|
||||
placeholders: operation.placeholders,
|
||||
language_slug: language_slug
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -25,7 +25,7 @@ defmodule Accent.Project do
|
||||
has_many(:all_collaborators, Accent.Collaborator)
|
||||
belongs_to(:language, Accent.Language)
|
||||
|
||||
field :machine_translations_config, MachineTranslationsConfig
|
||||
field(:machine_translations_config, MachineTranslationsConfig)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -10,6 +10,7 @@ defmodule Accent.GraphQL.Mutations.MachineTranslationsConfig do
|
||||
arg(:project_id, non_null(:id))
|
||||
arg(:provider, non_null(:string))
|
||||
arg(:use_platform, non_null(:boolean))
|
||||
arg(:enabled_actions, non_null(list_of(non_null(:string))))
|
||||
arg(:config_key, :string)
|
||||
|
||||
resolve(project_authorize(:save_project_machine_translations_config, &Resolver.save/3, :project_id))
|
||||
|
@ -10,6 +10,7 @@ defmodule Accent.GraphQL.Mutations.Revision do
|
||||
arg(:project_id, non_null(:id))
|
||||
arg(:language_id, non_null(:id))
|
||||
arg(:default_null, :boolean, default_value: false)
|
||||
arg(:machine_translations_enabled, :boolean, default_value: false)
|
||||
|
||||
resolve(project_authorize(:create_slave, &RevisionResolver.create/3, :project_id))
|
||||
end
|
||||
|
@ -155,6 +155,7 @@ defmodule Accent.GraphQL.Resolvers.Revision do
|
||||
defp parse_new_slave_options(args) do
|
||||
options = []
|
||||
options = if args[:default_null], do: ["default_null" | options], else: options
|
||||
options = if args[:machine_translations_enabled], do: ["machine_translations_enabled" | options], else: options
|
||||
|
||||
options
|
||||
end
|
||||
|
@ -19,6 +19,7 @@ defmodule Accent.GraphQL.Types.Project do
|
||||
|
||||
object :machine_translations_config do
|
||||
field(:provider, non_null(:string))
|
||||
field(:enabled_actions, non_null(list_of(non_null(:string))))
|
||||
field(:use_platform, non_null(:boolean))
|
||||
field(:use_config_key, non_null(:boolean))
|
||||
end
|
||||
@ -40,6 +41,7 @@ defmodule Accent.GraphQL.Types.Project do
|
||||
{:ok,
|
||||
%{
|
||||
provider: project.machine_translations_config["provider"],
|
||||
enabled_actions: project.machine_translations_config["enabled_actions"] || [],
|
||||
use_platform: project.machine_translations_config["use_platform"],
|
||||
use_config_key: not is_nil(project.machine_translations_config["config"]["key"])
|
||||
}}
|
||||
|
@ -11,6 +11,11 @@ defmodule Accent.MachineTranslations do
|
||||
Provider.id(provider)
|
||||
end
|
||||
|
||||
def enabled_on_action?(config, action) do
|
||||
config = config || %{}
|
||||
to_string(action) in Map.get(config, "enabled_actions", [])
|
||||
end
|
||||
|
||||
def enabled?(config) do
|
||||
provider = provider_from_config(config)
|
||||
Provider.enabled?(provider)
|
||||
|
@ -4,6 +4,6 @@ defmodule Accent.MachineTranslations.Provider.NotImplemented do
|
||||
defimpl Accent.MachineTranslations.Provider do
|
||||
def id(_provider), do: :not_implemented
|
||||
def enabled?(_provider), do: false
|
||||
def translate(_provider, entries, _source_language, _target_language), do: entries
|
||||
def translate(_provider, entries, _source_language, _target_language), do: {:ok, entries}
|
||||
end
|
||||
end
|
||||
|
@ -19,6 +19,7 @@ defmodule Movement.Builders.NewSlave do
|
||||
|
||||
defp process_operations(context = %Movement.Context{assigns: assigns, operations: operations}) do
|
||||
default_null = "default_null" in assigns.new_slave_options
|
||||
machine_translations_enabled = "machine_translations_enabled" in assigns.new_slave_options
|
||||
|
||||
new_operations =
|
||||
Enum.map(assigns[:translations], fn translation ->
|
||||
@ -33,10 +34,18 @@ defmodule Movement.Builders.NewSlave do
|
||||
plural: translation.plural,
|
||||
locked: translation.locked,
|
||||
placeholders: translation.placeholders,
|
||||
options: assigns.new_slave_options
|
||||
options: assigns.new_slave_options,
|
||||
machine_translations_enabled: machine_translations_enabled
|
||||
})
|
||||
end)
|
||||
|
||||
new_operations =
|
||||
if machine_translations_enabled do
|
||||
Movement.MachineTranslations.translate(new_operations, assigns[:project], assigns[:master_revision], assigns[:language])
|
||||
else
|
||||
new_operations
|
||||
end
|
||||
|
||||
%{context | operations: Enum.concat(operations, new_operations)}
|
||||
end
|
||||
|
||||
@ -56,6 +65,7 @@ defmodule Movement.Builders.NewSlave do
|
||||
|> RevisionScope.from_project(assigns[:project].id)
|
||||
|> RevisionScope.master()
|
||||
|> Repo.one!()
|
||||
|> Repo.preload(:language)
|
||||
|
||||
assign(context, :master_revision, master_revision)
|
||||
end
|
||||
|
@ -10,10 +10,13 @@ defmodule Movement.Builders.ProjectSync do
|
||||
|
||||
alias Accent.{Repo, Revision}
|
||||
|
||||
@batch_action "sync"
|
||||
|
||||
def build(context) do
|
||||
# Don’t keep track of the last used revision to prevent error on further steps
|
||||
context
|
||||
|> Map.put(:operations, [])
|
||||
|> assign(:batch_action, @batch_action)
|
||||
|> generate_operations()
|
||||
|> assign(:revision, nil)
|
||||
end
|
||||
@ -36,20 +39,18 @@ defmodule Movement.Builders.ProjectSync do
|
||||
context
|
||||
|> assign(:master_revision, master_revision)
|
||||
|> assign(:slave_revisions, slave_revisions)
|
||||
|> assign_revisions_operations
|
||||
|> assign_revisions_operations()
|
||||
end
|
||||
|
||||
defp assign_revisions_operations(context) do
|
||||
# Master revision
|
||||
# Slave revisions conflicts
|
||||
context =
|
||||
context
|
||||
|> assign(:master_revision, context.assigns[:master_revision])
|
||||
|> assign(:revision, context.assigns[:master_revision])
|
||||
|> RevisionSyncBuilder.build()
|
||||
|> assign(:revisions, context.assigns[:slave_revisions])
|
||||
|> SlaveConflictSyncBuilder.build()
|
||||
|
||||
# Slave revisions add/remove
|
||||
Enum.reduce(context.assigns[:slave_revisions], context, fn revision, acc ->
|
||||
acc
|
||||
|> assign(:revision, revision)
|
||||
|
@ -3,16 +3,32 @@ defmodule Movement.Builders.RevisionMerge do
|
||||
|
||||
import Movement.Context, only: [assign: 3]
|
||||
|
||||
alias Accent.Scopes.Revision, as: RevisionScope
|
||||
alias Accent.Scopes.Translation, as: TranslationScope
|
||||
alias Accent.{Repo, Translation}
|
||||
alias Accent.{Repo, Revision, Translation}
|
||||
alias Movement.EntriesCommitProcessor
|
||||
|
||||
@batch_action "merge"
|
||||
|
||||
def build(context) do
|
||||
context
|
||||
|> assign_translations()
|
||||
|> assign_master_revision()
|
||||
|> Movement.Context.assign(:batch_action, @batch_action)
|
||||
|> EntriesCommitProcessor.process()
|
||||
end
|
||||
|
||||
defp assign_master_revision(context) do
|
||||
master_revision =
|
||||
Revision
|
||||
|> RevisionScope.from_project(context.assigns[:project].id)
|
||||
|> RevisionScope.master()
|
||||
|> Repo.one!()
|
||||
|> Repo.preload(:language)
|
||||
|
||||
assign(context, :master_revision, master_revision)
|
||||
end
|
||||
|
||||
defp assign_translations(context) do
|
||||
translations =
|
||||
Translation
|
||||
|
@ -47,8 +47,8 @@ defmodule Movement.Comparers.MergeForce do
|
||||
{"conflict_on_proposed", _text} ->
|
||||
OperationMapper.map("merge_on_proposed_force", translation, suggested_translation)
|
||||
|
||||
{_action, _text} ->
|
||||
%Operation{action: "noop", key: translation.key}
|
||||
{_action, text} ->
|
||||
%Operation{action: "noop", key: translation.key, text: text}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -51,8 +51,8 @@ defmodule Movement.Comparers.MergePassive do
|
||||
|
||||
OperationMapper.map("merge_on_proposed", translation, suggested_translation)
|
||||
|
||||
{_action, _text} ->
|
||||
%Operation{action: "noop", key: translation.key}
|
||||
{_action, text} ->
|
||||
%Operation{action: "noop", key: translation.key, text: text}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -56,8 +56,8 @@ defmodule Movement.Comparers.MergeSmart do
|
||||
|
||||
OperationMapper.map("merge_on_corrected", translation, suggested_translation)
|
||||
|
||||
{_action, _text} ->
|
||||
%Operation{action: "noop", key: translation.key}
|
||||
{_action, text} ->
|
||||
%Operation{action: "noop", key: translation.key, text: text}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -34,8 +34,8 @@ defmodule Movement.Comparers.SyncPassive do
|
||||
|
||||
OperationMapper.map(action, translation, suggested_translation)
|
||||
|
||||
_ ->
|
||||
%Operation{action: "noop", key: suggested_translation.key}
|
||||
{_, text} ->
|
||||
%Operation{action: "noop", key: suggested_translation.key, text: text}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -33,8 +33,8 @@ defmodule Movement.Comparers.SyncSmart do
|
||||
"""
|
||||
def compare(translation, suggested_translation) do
|
||||
case TranslationComparer.compare(translation, suggested_translation.text) do
|
||||
{action, _text} when action in ~w(autocorrect) ->
|
||||
%Operation{action: action, key: translation.key}
|
||||
{action, text} when action === "autocorrect" ->
|
||||
%Operation{action: action, key: translation.key, text: text}
|
||||
|
||||
{action, text} ->
|
||||
suggested_translation = %{suggested_translation | text: text}
|
||||
|
@ -2,6 +2,8 @@ defmodule Movement.EntriesCommitProcessor do
|
||||
@no_action_keys ~w(noop autocorrect)
|
||||
@included_slave_actions ~w(new remove renew merge_on_corrected merge_on_proposed merge_on_proposed_force merge_on_corrected_force)
|
||||
|
||||
alias Movement.MachineTranslations
|
||||
|
||||
@doc """
|
||||
For list of translations, new data (like the content of a file upload) and a given function,
|
||||
returns the list of operations that will be executed. The operations will be neither persisted nor run.
|
||||
@ -33,9 +35,18 @@ defmodule Movement.EntriesCommitProcessor do
|
||||
}
|
||||
|
||||
operation = assigns[:comparer].(current_translation, suggested_translation)
|
||||
|
||||
operation =
|
||||
if MachineTranslations.enable_machine_translation?(operation, entry, assigns[:revision], assigns[:project], assigns[:batch_action]) do
|
||||
%{operation | machine_translations_enabled: true}
|
||||
else
|
||||
operation
|
||||
end
|
||||
|
||||
%{operation | options: assigns[:options]}
|
||||
end)
|
||||
|> filter_for_revision(assigns[:revision])
|
||||
|> MachineTranslations.translate(assigns[:project], assigns[:master_revision], assigns[:revision])
|
||||
|
||||
%{context | operations: Enum.concat(operations, new_operations)}
|
||||
end
|
||||
@ -73,15 +84,19 @@ defmodule Movement.EntriesCommitProcessor do
|
||||
end
|
||||
|
||||
defp filter_for_revision(operations, %{master: true}) do
|
||||
operations
|
||||
|> Enum.filter(fn %{action: operation} -> operation not in @no_action_keys end)
|
||||
Enum.filter(
|
||||
operations,
|
||||
fn operation ->
|
||||
operation.action not in @no_action_keys
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
defp filter_for_revision(operations, _) do
|
||||
Enum.filter(
|
||||
operations,
|
||||
fn %{action: operation} ->
|
||||
operation in @included_slave_actions and operation not in @no_action_keys
|
||||
fn operation ->
|
||||
operation.action in @included_slave_actions and operation.action not in @no_action_keys
|
||||
end
|
||||
)
|
||||
end
|
||||
|
46
lib/movement/machine_translations.ex
Normal file
46
lib/movement/machine_translations.ex
Normal file
@ -0,0 +1,46 @@
|
||||
defmodule Movement.MachineTranslations do
|
||||
alias Accent.{Language, MachineTranslations, Operation, Revision}
|
||||
|
||||
def enable_machine_translation?(_operation, _entry, %{master: true}, _, _), do: false
|
||||
|
||||
def enable_machine_translation?(operation, entry, _, project, action) do
|
||||
MachineTranslations.enabled?(project.machine_translations_config) and
|
||||
MachineTranslations.enabled_on_action?(project.machine_translations_config, action) and
|
||||
operation.text === entry.value
|
||||
end
|
||||
|
||||
def translate(operations, _project, nil, _revision), do: operations
|
||||
|
||||
def translate(operations, project, master_revision, revision) do
|
||||
translated_texts =
|
||||
operations
|
||||
|> Enum.filter(& &1.machine_translations_enabled)
|
||||
|> Enum.map(&Operation.to_langue_entry(&1, master_revision.id === &1.revision_id, language_slug(revision)))
|
||||
|> MachineTranslations.translate(%{slug: language_slug(master_revision)}, %{slug: language_slug(revision)}, project.machine_translations_config)
|
||||
|> case do
|
||||
entries when is_list(entries) ->
|
||||
Map.new(Enum.map(entries, &{&1.id, &1.value}))
|
||||
|
||||
_ ->
|
||||
%{}
|
||||
end
|
||||
|
||||
Enum.map(operations, fn operation ->
|
||||
with true <- operation.machine_translations_enabled,
|
||||
text = Map.get(translated_texts, operation.key),
|
||||
true <- text !== operation.text do
|
||||
dbg(%{operation | text: text, machine_translated: true})
|
||||
else
|
||||
_ -> operation
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp language_slug(%Revision{} = revision) do
|
||||
revision.slug || revision.language.slug
|
||||
end
|
||||
|
||||
defp language_slug(%Language{} = language) do
|
||||
language.slug
|
||||
end
|
||||
end
|
@ -17,6 +17,7 @@ defmodule Movement.Mappers.Operation do
|
||||
version_id: Map.get(suggested_translation, :version_id),
|
||||
translation_id: Map.get(suggested_translation, :translation_id),
|
||||
previous_translation: PreviousTranslation.from_translation(current_translation),
|
||||
machine_translations_enabled: suggested_translation.machine_translations_enabled,
|
||||
placeholders: suggested_translation.placeholders
|
||||
}
|
||||
end
|
||||
@ -35,6 +36,7 @@ defmodule Movement.Mappers.Operation do
|
||||
plural: Map.get(suggested_translation, :plural, current_translation.plural),
|
||||
locked: Map.get(suggested_translation, :locked, current_translation.locked),
|
||||
translation_id: Map.get(current_translation, :id),
|
||||
machine_translations_enabled: Map.get(suggested_translation, :machine_translations_enabled, false),
|
||||
previous_translation: PreviousTranslation.from_translation(current_translation),
|
||||
placeholders: Map.get(suggested_translation, :placeholders, current_translation.placeholders)
|
||||
}
|
||||
|
@ -20,7 +20,9 @@ defmodule Movement.Operation do
|
||||
project_id: nil,
|
||||
previous_translation: nil,
|
||||
placeholders: [],
|
||||
options: []
|
||||
options: [],
|
||||
machine_translated: false,
|
||||
machine_translations_enabled: false
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
end
|
||||
|
@ -100,6 +100,7 @@ defmodule Movement.Persisters.Base do
|
||||
version_id: operation.version_id || placeholder_values[:version_id],
|
||||
revision_id: operation.revision_id || placeholder_values[:revision_id]
|
||||
})
|
||||
|> Map.delete(:machine_translations_enabled)
|
||||
end)
|
||||
|> Stream.chunk_every(@operations_inserts_chunk)
|
||||
|> Stream.flat_map(fn operations ->
|
||||
|
@ -26,6 +26,7 @@ defmodule Movement.Persisters.NewSlave do
|
||||
|> Repo.insert()
|
||||
|> case do
|
||||
{:ok, revision} ->
|
||||
revision = Repo.preload(revision, :language)
|
||||
Movement.Context.assign(context, :revision, revision)
|
||||
|
||||
{:error, changeset} ->
|
||||
|
@ -6,15 +6,12 @@ defmodule Movement.Persisters.ProjectSync do
|
||||
alias Accent.{Document, Project, Repo}
|
||||
alias Movement.Persisters.Base, as: BasePersister
|
||||
|
||||
@batch_action "sync"
|
||||
|
||||
def persist(context = %Movement.Context{operations: []}), do: {:ok, {context, []}}
|
||||
|
||||
def persist(context) do
|
||||
Repo.transaction(fn ->
|
||||
context
|
||||
|> persist_document()
|
||||
|> Movement.Context.assign(:batch_action, @batch_action)
|
||||
|> BasePersister.execute()
|
||||
|> case do
|
||||
{context, operations} ->
|
||||
|
@ -4,13 +4,7 @@ defmodule Movement.Persisters.RevisionMerge do
|
||||
alias Accent.Repo
|
||||
alias Movement.Persisters.Base, as: BasePersister
|
||||
|
||||
@batch_action "merge"
|
||||
|
||||
def persist(context) do
|
||||
Repo.transaction(fn ->
|
||||
context
|
||||
|> Movement.Context.assign(:batch_action, @batch_action)
|
||||
|> BasePersister.execute()
|
||||
end)
|
||||
Repo.transaction(fn -> BasePersister.execute(context) end)
|
||||
end
|
||||
end
|
||||
|
@ -1,4 +1,4 @@
|
||||
defmodule Movement.SuggestedTranslation do
|
||||
@enforce_keys ~w(text)a
|
||||
defstruct ~w(text key file_comment file_index value_type revision_id translation_id version_id plural locked placeholders)a
|
||||
defstruct ~w(text key file_comment file_index value_type revision_id translation_id version_id plural locked placeholders machine_translations_enabled)a
|
||||
end
|
||||
|
@ -42,7 +42,6 @@ defmodule Accent.LintController do
|
||||
lint_translations =
|
||||
conn.assigns[:context].entries
|
||||
|> Enum.map(&map_entry(&1, conn))
|
||||
|> dbg
|
||||
|> Accent.Lint.lint()
|
||||
|> map_lint_translations()
|
||||
|
||||
|
@ -12,7 +12,7 @@ defmodule Accent.MachineTranslationsController do
|
||||
plug(:fetch_master_revision)
|
||||
plug(:load_resource, model: Language, id_name: "language", as: :source_language)
|
||||
plug(:load_resource, model: Language, id_name: "to_language_id", as: :target_language)
|
||||
plug(:load_resource, model: Document, id_name: "document_id", as: :document, only: [:machine_translations_translate])
|
||||
plug(:fetch_document when action === :translate_document)
|
||||
plug(:fetch_order when action === :translate_document)
|
||||
plug(:fetch_format when action === :translate_document)
|
||||
plug(Accent.Plugs.MovementContextParser when action === :translate_file)
|
||||
@ -130,6 +130,10 @@ defmodule Accent.MachineTranslationsController do
|
||||
assign(conn, :document_format, conn.assigns[:document].format)
|
||||
end
|
||||
|
||||
defp fetch_document(conn, _) do
|
||||
assign(conn, :document, Repo.get(Document, conn.params["document_id"]))
|
||||
end
|
||||
|
||||
defp fetch_order(conn = %{params: %{"order_by" => ""}}, _), do: assign(conn, :order, "index")
|
||||
defp fetch_order(conn = %{params: %{"order_by" => order}}, _), do: assign(conn, :order, order)
|
||||
defp fetch_order(conn, _), do: assign(conn, :order, "index")
|
||||
|
@ -84,7 +84,7 @@ defmodule Accent.Plugs.MovementContextParser do
|
||||
end
|
||||
|
||||
def assign_movement_context(conn, _) do
|
||||
assign(conn, :movement_context, %Context{})
|
||||
assign(conn, :movement_context, %Context{assigns: %{options: [], project: conn.assigns[:project]}})
|
||||
end
|
||||
|
||||
def assign_movement_version(conn = %{assigns: %{version: version, movement_context: context}}, _opts) do
|
||||
|
@ -0,0 +1,9 @@
|
||||
defmodule Accent.Repo.Migrations.RemoveCommentIdFromOperations do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:operations) do
|
||||
remove(:comment_id)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,15 @@
|
||||
defmodule Accent.Repo.Migrations.AddMachineTranslatedForOperations do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:operations) do
|
||||
add(:machine_translated, :boolean)
|
||||
end
|
||||
|
||||
execute("UPDATE operations SET machine_translated = false")
|
||||
|
||||
alter table(:operations) do
|
||||
modify(:machine_translated, :boolean, default: false, null: false)
|
||||
end
|
||||
end
|
||||
end
|
@ -54,6 +54,7 @@ defmodule AccentTest.Movement.Persisters.ProjectSync do
|
||||
|> Context.assign(:document, document)
|
||||
|> Context.assign(:revision, revision)
|
||||
|> Context.assign(:user_id, user.id)
|
||||
|> Context.assign(:batch_action, "sync")
|
||||
|> ProjectSyncPersister.persist()
|
||||
|
||||
batch_operation =
|
||||
|
@ -380,6 +380,7 @@
|
||||
"machine_translations": {
|
||||
"title": "Machine translations",
|
||||
"text": "Add a valid configuration to enable translating directly in the Review page. You can also translate your project’s file as well as arbirtary files that you upload on Accent.",
|
||||
"enabled_actions_sync": "Automatically run on project sync",
|
||||
"use_platform_label": "Use the platform config if available",
|
||||
"config_key_help_present": "Config key has been set but the value will never be exposed via the API.",
|
||||
"config_key_help_not_present": "Once saved, the config key will never be shown.",
|
||||
@ -700,6 +701,7 @@
|
||||
},
|
||||
"project_manage_languages_create_form": {
|
||||
"default_null": "Initialize language with empty strings",
|
||||
"machine_translations_enabled": "Initialize language with machine translations",
|
||||
"language_search_placeholder": "Search…",
|
||||
"save_button": "Add language"
|
||||
},
|
||||
|
@ -381,6 +381,7 @@
|
||||
"title": "Traductions machines",
|
||||
"text": "Ajoutez une configuration valide pour activer la traduction directement dans la page Réviser. Vous pouvez également traduire le fichier de votre projet ainsi que les fichiers arbirtaires que vous téléchargez sur Accent.",
|
||||
"use_platform_label": "Utilisez la configuration de la plate-forme si disponible",
|
||||
"enabled_actions_sync": "Exécution automatique lors de la synchronisation du projet",
|
||||
"config_key_help_present": "La clé de configuration a été définie, la valeur ne sera jamais exposée via l’API.",
|
||||
"config_key_help_not_present": "Une fois enregistrée, la clé de configuration ne sera plus jamais affichée.",
|
||||
"config_key_placeholder": "Copiez la clé de configuration ici…"
|
||||
@ -700,6 +701,7 @@
|
||||
},
|
||||
"project_manage_languages_create_form": {
|
||||
"default_null": "Initialiser le langage avec des chaînes vides",
|
||||
"machine_translations_enabled": "Initialiser le langage avec des traductions externes",
|
||||
"language_search_placeholder": "Recherche…",
|
||||
"save_button": "Ajouter une langue"
|
||||
},
|
||||
|
@ -11,6 +11,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 auto;
|
||||
padding: 0 30px;
|
||||
max-width: var(--screen-lg);
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,11 @@ export default class ProjectCreateForm extends Component<Args> {
|
||||
this.logo = selection.emoji;
|
||||
}
|
||||
|
||||
@action
|
||||
autofocus(input: HTMLInputElement) {
|
||||
input.focus();
|
||||
}
|
||||
|
||||
@action
|
||||
async submit() {
|
||||
this.isCreating = true;
|
||||
|
@ -17,7 +17,7 @@
|
||||
</label>
|
||||
|
||||
<div local-class='formItem-fields'>
|
||||
<input value={{this.name}} local-class='textInput' {{on 'keyup' (fn this.setName)}} />
|
||||
<input {{did-insert (fn this.autofocus)}} value={{this.name}} autofocus local-class='textInput' {{on 'keyup' (fn this.setName)}} />
|
||||
|
||||
<input type='color' value={{this.mainColor}} local-class='colorInput' {{on 'change' (fn this.setMainColor)}} />
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
</LinkTo>
|
||||
|
||||
<LinkTo @route='logged-in.project.edit.machine-translations' local-class='link'>
|
||||
{{inline-svg 'assets/machine-translations.svg' local-class='link-icon link-icon--machine-translations'}}
|
||||
{{inline-svg 'assets/rocket.svg' local-class='link-icon link-icon--machine-translations'}}
|
||||
<strong>{{t 'components.project_settings.links_list.machine_translations'}}</strong>
|
||||
<p>{{t 'components.project_settings.links_list.machine_translations_text'}}</p>
|
||||
</LinkTo>
|
||||
|
@ -13,10 +13,12 @@ interface Args {
|
||||
onDelete: () => void;
|
||||
onSave: ({
|
||||
provider,
|
||||
enabledActions,
|
||||
usePlatform,
|
||||
configKey,
|
||||
}: {
|
||||
provider: string;
|
||||
enabledActions: string[];
|
||||
usePlatform: boolean;
|
||||
configKey: string | null;
|
||||
}) => Promise<any>;
|
||||
@ -41,6 +43,10 @@ export default class ProjectSettingsMachineTranslations extends Component<Args>
|
||||
@service('intl')
|
||||
intl: IntlService;
|
||||
|
||||
@tracked
|
||||
enabledActions =
|
||||
this.args.project.machineTranslationsConfig?.enabledActions || [];
|
||||
|
||||
@tracked
|
||||
provider =
|
||||
this.args.project.machineTranslationsConfig?.provider || 'google_translate';
|
||||
@ -52,6 +58,10 @@ export default class ProjectSettingsMachineTranslations extends Component<Args>
|
||||
@tracked
|
||||
configKey: any;
|
||||
|
||||
get enabledActionsSync() {
|
||||
return this.enabledActions.includes('sync');
|
||||
}
|
||||
|
||||
get providerValue() {
|
||||
return this.mappedProviders.find(({value}) => value === this.provider);
|
||||
}
|
||||
@ -94,6 +104,17 @@ export default class ProjectSettingsMachineTranslations extends Component<Args>
|
||||
this.provider = value;
|
||||
}
|
||||
|
||||
@action
|
||||
onEnabledActionsChange(action: string) {
|
||||
if (this.enabledActions.includes(action)) {
|
||||
this.enabledActions = this.enabledActions.filter(
|
||||
(enabledAction: string) => enabledAction !== action
|
||||
);
|
||||
} else {
|
||||
this.enabledActions = this.enabledActions.concat([action]);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onUsePlatformChange(event: InputEvent) {
|
||||
const checked = (event.target as HTMLInputElement).checked;
|
||||
@ -111,6 +132,7 @@ export default class ProjectSettingsMachineTranslations extends Component<Args>
|
||||
*submit() {
|
||||
yield this.args.onSave({
|
||||
provider: this.provider,
|
||||
enabledActions: this.enabledActions,
|
||||
usePlatform: this.usePlatform,
|
||||
configKey: this.configKey,
|
||||
});
|
||||
|
@ -60,10 +60,17 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.platform-checkbox {
|
||||
.options {
|
||||
margin: 10px 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.option-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 270px;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
|
@ -17,15 +17,21 @@
|
||||
@options={{this.mappedProviders}}
|
||||
@onchange={{fn this.setProvider}}
|
||||
/>
|
||||
<label local-class='platform-checkbox'>
|
||||
</div>
|
||||
|
||||
<div local-class='options'>
|
||||
<label local-class='option-checkbox'>
|
||||
<input value={{this.usePlatform}} type='checkbox' checked={{this.usePlatform}} {{on 'change' (fn this.onUsePlatformChange)}} />
|
||||
{{t 'components.project_settings.machine_translations.use_platform_label'}}
|
||||
</label>
|
||||
|
||||
<label local-class='option-checkbox'>
|
||||
<input value={{this.enabledActionsSync}} type='checkbox' checked={{this.enabledActionsSync}} {{on 'change' (fn this.onEnabledActionsChange 'sync')}} />
|
||||
{{t 'components.project_settings.machine_translations.enabled_actions_sync'}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{#unless this.usePlatform}}
|
||||
|
||||
{{#if @project.machineTranslationsConfig.useConfigKey}}
|
||||
<p local-class='config-key-help'>
|
||||
{{t 'components.project_settings.machine_translations.config_key_help_present'}}
|
||||
|
@ -29,6 +29,9 @@ export default class CreateForm extends Component<Args> {
|
||||
@tracked
|
||||
defaultNull = false;
|
||||
|
||||
@tracked
|
||||
machineTranslationsEnabled = false;
|
||||
|
||||
@not('language')
|
||||
emptyLanguage: boolean;
|
||||
|
||||
@ -49,11 +52,19 @@ export default class CreateForm extends Component<Args> {
|
||||
this.defaultNull = !this.defaultNull;
|
||||
}
|
||||
|
||||
@action
|
||||
onChangeMachineTranslationsEnabled() {
|
||||
this.machineTranslationsEnabled = !this.machineTranslationsEnabled;
|
||||
}
|
||||
|
||||
@action
|
||||
async submit() {
|
||||
this.isLoading = true;
|
||||
|
||||
await this.args.onCreate(this.language, {defaultNull: this.defaultNull});
|
||||
await this.args.onCreate(this.language, {
|
||||
machineTranslationsEnabled: this.machineTranslationsEnabled,
|
||||
defaultNull: this.defaultNull,
|
||||
});
|
||||
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
@ -1,24 +1,40 @@
|
||||
.fields {
|
||||
.form {
|
||||
margin-bottom: 25px;
|
||||
padding: 12px;
|
||||
border-radius: 3px;
|
||||
background: var(--background-light);
|
||||
border: 1px solid var(--background-light-highlight);
|
||||
|
||||
:global(.ember-power-select-trigger) {
|
||||
min-height: 31px;
|
||||
max-width: 400px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--background-light);
|
||||
background: var(--body-background);
|
||||
border: 1px solid var(--background-light-highlight);
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.option {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.optionLabel {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.optionLabelText {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.optionLabel-icon {
|
||||
width: 15px;
|
||||
margin-left: 3px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div local-class='fields'>
|
||||
<div local-class='form'>
|
||||
<AccSelect
|
||||
@search={{fn this.searchLanguages}}
|
||||
@searchEnabled={{true}}
|
||||
@ -9,16 +9,31 @@
|
||||
@onchange={{fn this.setLanguage}}
|
||||
/>
|
||||
|
||||
<div local-class='option'>
|
||||
<label local-class='optionLabel'>
|
||||
<input type='checkbox' checked={{this.defaultNull}} {{on 'change' (fn this.onChangeDefaultNull)}} />
|
||||
<span local-class='optionLabelText'>
|
||||
{{t 'components.project_manage_languages_create_form.default_null'}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div local-class='options'>
|
||||
<div local-class='option'>
|
||||
<label local-class='optionLabel'>
|
||||
<input type='checkbox' checked={{this.defaultNull}} {{on 'change' (fn this.onChangeDefaultNull)}} />
|
||||
<span local-class='optionLabelText'>
|
||||
{{t 'components.project_manage_languages_create_form.default_null'}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<AsyncButton @onClick={{fn this.submit}} class='button button--filled' @loading={{this.isLoading}} @disabled={{this.emptyLanguage}}>
|
||||
{{t 'components.project_manage_languages_create_form.save_button'}}
|
||||
</AsyncButton>
|
||||
{{#if (get @permissions 'machine_translations_translate')}}
|
||||
<div local-class='option'>
|
||||
<label local-class='optionLabel'>
|
||||
<input type='checkbox' checked={{this.machineTranslationsEnabled}} {{on 'change' (fn this.onChangeMachineTranslationsEnabled)}} />
|
||||
<span local-class='optionLabelText'>
|
||||
{{t 'components.project_manage_languages_create_form.machine_translations_enabled'}}
|
||||
</span>
|
||||
|
||||
{{inline-svg 'assets/rocket.svg' local-class='optionLabel-icon'}}
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<AsyncButton @onClick={{fn this.submit}} class='button button--filled' @loading={{this.isLoading}} @disabled={{this.emptyLanguage}}>
|
||||
{{t 'components.project_manage_languages_create_form.save_button'}}
|
||||
</AsyncButton>
|
||||
</div>
|
@ -19,7 +19,8 @@
|
||||
padding: 10px 2px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom-color: var(--background-light-highlight);
|
||||
font-size: 16px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
@ -74,7 +75,7 @@
|
||||
|
||||
.list-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
|
||||
|
@ -11,13 +11,13 @@
|
||||
{{#if (get @permissions 'create_slave')}}
|
||||
<div local-class='createSlaveForm'>
|
||||
{{#if @errors}}
|
||||
<div local-class='error'>
|
||||
<ul local-class='error'>
|
||||
{{#each @errors as |error|}}
|
||||
<li>
|
||||
{{error}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</div>
|
||||
</ul>
|
||||
{{/if}}
|
||||
<ProjectSettings::ManageLanguages::CreateForm @permissions={{@permissions}} @project={{@project}} @languages={{@languages}} @onCreate={{@onCreate}} />
|
||||
</div>
|
||||
|
@ -7,7 +7,9 @@ import GlobalState from 'accent-webapp/services/global-state';
|
||||
import FlashMessages from 'ember-cli-flash/services/flash-messages';
|
||||
import ApolloMutate from 'accent-webapp/services/apollo-mutate';
|
||||
|
||||
import machineTranslationsConfigSaveQuery from 'accent-webapp/queries/save-project-machine-translations-config';
|
||||
import machineTranslationsConfigSaveQuery, {
|
||||
SaveProjectMachineTranslationsConfigVariables,
|
||||
} from 'accent-webapp/queries/save-project-machine-translations-config';
|
||||
import machineTranslationsConfigDeleteQuery from 'accent-webapp/queries/delete-project-machine-translations-config';
|
||||
|
||||
const FLASH_MESSAGE_PREFIX = 'pods.project.edit.flash_messages.';
|
||||
@ -42,16 +44,14 @@ export default class MachineTranslationController extends Controller {
|
||||
showLoading: boolean;
|
||||
|
||||
@action
|
||||
async saveMachineTranslationsConfig(config: {
|
||||
provider: string;
|
||||
usePlatform: boolean;
|
||||
configKey: string | null;
|
||||
}) {
|
||||
async saveMachineTranslationsConfig(
|
||||
config: SaveProjectMachineTranslationsConfigVariables
|
||||
) {
|
||||
return this.mutateResource({
|
||||
mutation: machineTranslationsConfigSaveQuery,
|
||||
successMessage: FLASH_MESSAGE_CONFIG_SUCCESS,
|
||||
errorMessage: FLASH_MESSAGE_CONFIG_ERROR,
|
||||
variables: {projectId: this.project.id, ...config},
|
||||
variables: {...config, projectId: this.project.id},
|
||||
});
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ export default class MachineTranslationController extends Controller {
|
||||
const response = await this.apolloMutate.mutate({
|
||||
mutation,
|
||||
variables,
|
||||
refetchQueries: ['ProjectMachineTranslationsConfig'],
|
||||
refetchQueries: ['Project', 'ProjectMachineTranslationsConfig'],
|
||||
});
|
||||
|
||||
if (response.errors) {
|
||||
|
@ -112,7 +112,10 @@ export default class ManageLanguagesController extends Controller {
|
||||
}
|
||||
|
||||
@action
|
||||
async create(languageId: string, options: {defaultNull: boolean}) {
|
||||
async create(
|
||||
languageId: string,
|
||||
options: {defaultNull: boolean; machineTranslationsEnabled: boolean}
|
||||
) {
|
||||
const project = this.model.project;
|
||||
this.errors = [];
|
||||
|
||||
@ -123,6 +126,7 @@ export default class ManageLanguagesController extends Controller {
|
||||
projectId: project.id,
|
||||
languageId,
|
||||
defaultNull: options.defaultNull,
|
||||
machineTranslationsEnabled: options.machineTranslationsEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -5,11 +5,13 @@ export default gql`
|
||||
$projectId: ID!
|
||||
$languageId: ID!
|
||||
$defaultNull: Boolean
|
||||
$machineTranslationsEnabled: Boolean
|
||||
) {
|
||||
createRevision(
|
||||
projectId: $projectId
|
||||
languageId: $languageId
|
||||
defaultNull: $defaultNull
|
||||
machineTranslationsEnabled: $machineTranslationsEnabled
|
||||
) {
|
||||
revision {
|
||||
id
|
||||
|
@ -8,6 +8,7 @@ export default gql`
|
||||
machineTranslationsConfig {
|
||||
provider
|
||||
usePlatform
|
||||
enabledActions
|
||||
useConfigKey
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export interface SaveProjectMachineTranslationsConfigVariables {
|
||||
name: string;
|
||||
projectId: string;
|
||||
pictureUrl: string;
|
||||
permissions: string[];
|
||||
provider: string;
|
||||
configKey: string;
|
||||
enabledActions: string[];
|
||||
usePlatform: boolean;
|
||||
}
|
||||
|
||||
export interface SaveProjectMachineTranslationsConfigResponse {
|
||||
@ -20,12 +21,14 @@ export default gql`
|
||||
$provider: String!
|
||||
$configKey: String
|
||||
$usePlatform: Boolean!
|
||||
$enabledActions: [String!]!
|
||||
$projectId: ID!
|
||||
) {
|
||||
saveProjectMachineTranslationsConfig(
|
||||
provider: $provider
|
||||
configKey: $configKey
|
||||
usePlatform: $usePlatform
|
||||
enabledActions: $enabledActions
|
||||
projectId: $projectId
|
||||
) {
|
||||
project {
|
||||
|
22
webapp/public/assets/rocket.svg
Normal file
22
webapp/public/assets/rocket.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M16.061 10.404L14 17h-4l-2.061-6.596a6 6 0 01.998-5.484l2.59-3.315a.6.6 0 01.946 0l2.59 3.315a6 6 0 01.998 5.484zM10 20c0 2 2 3 2 3s2-1 2-3M8.5 12.5C5 15 7 19 7 19l3-2M15.931 12.5c3.5 2.5 1.5 6.5 1.5 6.5l-3-2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="M12 11a2 2 0 110-4 2 2 0 010 4z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
</svg>
|
After Width: | Height: | Size: 671 B |
Loading…
Reference in New Issue
Block a user