Skip to content

Commit

Permalink
Add basic support for XLIFF 1.2 file format
Browse files Browse the repository at this point in the history
  • Loading branch information
simonprev committed Mar 29, 2019
1 parent c373713 commit fe69c3c
Show file tree
Hide file tree
Showing 25 changed files with 317 additions and 49 deletions.
8 changes: 5 additions & 3 deletions lib/accent/schemas/document_format.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ defmodule Accent.DocumentFormat do
%{name: "Java properties XML", slug: "java_properties_xml", extension: "xml"},
%{name: "CSV", slug: "csv", extension: "csv"},
%{name: "Laravel PHP", slug: "laravel_php", extension: "php"},
%{name: "Go I18n JSON", slug: "go_i18n_json", extension: "json"}
%{name: "Go I18n JSON", slug: "go_i18n_json", extension: "json"},
%{name: "XLIFF 1.2", slug: "xliff_1_2", extension: "xlf"}
]

@doc """
Slugs used in document changeset validation
## Examples
iex> Accent.DocumentFormat.slugs()
["simple_json", "json", "strings", "gettext", "rails_yml", "es6_module", "android_xml", "java_properties", "java_properties_xml", "csv", "laravel_php", "go_i18n_json"]
["simple_json", "json", "strings", "gettext", "rails_yml", "es6_module", "android_xml", "java_properties", "java_properties_xml", "csv", "laravel_php", "go_i18n_json", "xliff_1_2"]
"""
defmacro slugs, do: Enum.map(@all, &Map.get(&1, :slug))

Expand All @@ -44,7 +45,8 @@ defmodule Accent.DocumentFormat do
%Accent.DocumentFormat{extension: "xml", name: "Java properties XML", slug: "java_properties_xml"},
%Accent.DocumentFormat{extension: "csv", name: "CSV", slug: "csv"},
%Accent.DocumentFormat{extension: "php", name: "Laravel PHP", slug: "laravel_php"},
%Accent.DocumentFormat{extension: "json", name: "Go I18n JSON", slug: "go_i18n_json"}
%Accent.DocumentFormat{extension: "json", name: "Go I18n JSON", slug: "go_i18n_json"},
%Accent.DocumentFormat{extension: "xlf", name: "XLIFF 1.2", slug: "xliff_1_2"}
]
"""
def all, do: Enum.map(@all, &struct(__MODULE__, &1))
Expand Down
33 changes: 25 additions & 8 deletions lib/accent/translations/translations_renderer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ defmodule Accent.TranslationsRenderer do

def render(args) do
value_map = Map.get(args, :value_map, & &1.corrected_text)
serializer = fetch_serializer(args[:document_format])
entries = fetch_entries(args[:translations], value_map)
serializer = fetch_serializer(args[:document].format)
master_translations = Enum.group_by(args[:master_translations], & &1.key)
entries = fetch_entries(args[:translations], master_translations, value_map)

parser_result = %Langue.Formatter.ParserResult{
serialzier_input = %Langue.Formatter.ParserResult{
entries: entries,
language: args[:language],
top_of_the_file_comment: args[:document_top_of_the_file_comment],
header: args[:document_header]
language: %Langue.Language{
slug: args[:language].slug,
plural_forms: args[:language].plural_forms
},
document: %Langue.Document{
path: args[:document].path,
master_language: args[:master_revision].language.slug,
top_of_the_file_comment: args[:document].top_of_the_file_comment,
header: args[:document].header
}
}

try do
serializer.(parser_result)
serializer.(serialzier_input)
rescue
_ -> Langue.Formatter.SerializerResult.empty()
end
Expand All @@ -26,9 +34,12 @@ defmodule Accent.TranslationsRenderer do
end
end

defp fetch_entries(translations, value_map) do
defp fetch_entries(translations, master_translations, value_map) do
Enum.map(translations, fn translation ->
master_translation = Map.get(master_translations, translation.key)

%Langue.Entry{
master_value: fetch_master_value(master_translation, value_map),
key: translation.key,
value: value_map.(translation),
comment: translation.file_comment,
Expand All @@ -37,4 +48,10 @@ defmodule Accent.TranslationsRenderer do
}
end)
end

defp fetch_master_value(nil, _), do: nil
defp fetch_master_value([], _), do: nil
defp fetch_master_value([master_translation], value_map) do
value_map.(master_translation)
end
end
1 change: 1 addition & 0 deletions lib/graphql/types/document_format.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Accent.GraphQL.Types.DocumentFormat do
value(:csv, as: "csv")
value(:laravel_php, as: "laravel_php")
value(:go_i18n_json, as: "go_i18n_json")
value(:xliff_1_2, as: "xliff_1_2")
end

object :document_format_item do
Expand Down
3 changes: 3 additions & 0 deletions lib/langue/document.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Langue.Document do
defstruct path: nil, header: nil, top_of_the_file_comment: nil, master_language: nil
end
2 changes: 1 addition & 1 deletion lib/langue/entry.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Langue.Entry do
defstruct key: nil, value: nil, comment: nil, index: 1, value_type: "string", locked: false, plural: false, placeholders: []
defstruct key: nil, master_value: nil, value: nil, comment: nil, index: 1, value_type: "string", locked: false, plural: false, placeholders: []

@type t :: %__MODULE__{
key: binary() | nil,
Expand Down
9 changes: 6 additions & 3 deletions lib/langue/formatter/gettext/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ defmodule Langue.Formatter.Gettext.Parser do
alias Langue.Entry
alias Langue.Utils.Placeholders

def parse(%{render: render}) do
def parse(%{render: render, document: document}) do
{:ok, po} = Gettext.PO.parse_string(render)
entries = parse_translations(po)
top_of_the_file_comment = join_string(po.top_of_the_file_comments)
header = join_string(po.headers)

%Langue.Formatter.ParserResult{
entries: entries,
top_of_the_file_comment: top_of_the_file_comment,
header: header
document: %{
document
| top_of_the_file_comment: top_of_the_file_comment,
header: header
}
}
end

Expand Down
6 changes: 3 additions & 3 deletions lib/langue/formatter/gettext/serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ defmodule Langue.Formatter.Gettext.Serializer do

alias Langue.Utils.NestedParserHelper

def serialize(%{entries: entries, top_of_the_file_comment: top_of_the_file_comment, header: header, language: language}) do
def serialize(%{entries: entries, document: document, language: language}) do
comments =
top_of_the_file_comment
document.top_of_the_file_comment
|> String.trim()
|> String.split("\n", trim: true)

headers =
header
document.header
|> String.trim()
|> String.replace("\"", "")
|> replace_language_header(language)
Expand Down
2 changes: 1 addition & 1 deletion lib/langue/formatter/parser_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Langue.Formatter.ParserResult do
@type t :: struct

@enforce_keys [:entries]
defstruct entries: [], top_of_the_file_comment: "", header: "", language: nil
defstruct entries: [], document: nil, language: nil

def empty, do: %__MODULE__{entries: []}
end
2 changes: 1 addition & 1 deletion lib/langue/formatter/serializer_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Langue.Formatter.SerializerResult do
@type t :: struct

@enforce_keys [:render]
defstruct render: ""
defstruct render: "", document: nil

def empty, do: %__MODULE__{render: ""}
end
41 changes: 41 additions & 0 deletions lib/langue/formatter/xliff_1_2/parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Langue.Formatter.XLIFF12.Parser do
@behaviour Langue.Formatter.Parser

alias Langue.Entry
alias Langue.Utils.Placeholders

def parse(%{render: render}) do
render
|> :erlsom.simple_form()
|> case do
{:ok, {'file', _attributes, [{'body', _, body}]}, _} ->
entries =
body
|> Enum.with_index(1)
|> Enum.map(&parse_line/1)
|> Enum.reject(&is_nil/1)
|> Placeholders.parse(Langue.Formatter.XLIFF12.placeholder_regex())

%Langue.Formatter.ParserResult{entries: entries}

_ ->
Langue.Formatter.ParserResult.empty()
end
end

defp parse_line({{'trans-unit', [{'id', key}], [{'source', [], [source]}, {'target', [], [value]}]}, index}) do
key = IO.chardata_to_string(key)
value = IO.chardata_to_string(value)
source = IO.chardata_to_string(source)

%Entry{
value: value,
master_value: source,
value_type: Langue.ValueType.parse(value),
key: key,
index: index
}
end

defp parse_line(_), do: nil
end
63 changes: 63 additions & 0 deletions lib/langue/formatter/xliff_1_2/serializer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Langue.Formatter.XLIFF12.Serializer do
@behaviour Langue.Formatter.Serializer

def serialize(%{entries: entries, language: %{slug: slug}, document: %{master_language: master_language, path: path}}) when slug === master_language do
file_attributes = [
original: path,
datatype: "plaintext",
"source-language": master_language
]

body = [
{"body", [], Enum.map(entries, &parse_master/1)}
]

serialize_body(file_attributes, body)
end

def serialize(%{entries: entries, language: language, document: document}) do
file_attributes = [
original: document.path,
datatype: "plaintext",
"source-language": document.master_language,
"target-language": language.slug
]

body = [
{"body", [], Enum.map(entries, &parse_target/1)}
]

serialize_body(file_attributes, body)
end

defp serialize_body(file_attributes, body) do
render =
{"file", file_attributes, body}
|> XmlBuilder.generate()
|> Kernel.<>("\n")

%Langue.Formatter.SerializerResult{render: render}
end

defp parse_target(%{key: key, master_value: master_value, value: value}) do
{
"trans-unit",
[id: key],
[
{"source", [], master_value},
{"target", [], value}
]
}
end

defp parse_master(%{key: key, value: value}) do
{
"trans-unit",
[id: key],
[
{"source", [], value},
{"target", [], ""}
]
}
end
end
11 changes: 11 additions & 0 deletions lib/langue/formatter/xliff_1_2/xliff_1_2.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Langue.Formatter.XLIFF12 do
@behaviour Langue.Formatter

alias Langue.Formatter.XLIFF12.{Parser, Serializer}

def name, do: "xliff_1_2"
def placeholder_regex, do: :not_supported

defdelegate parse(map), to: Parser
defdelegate serialize(map), to: Serializer
end
3 changes: 3 additions & 0 deletions lib/langue/language.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Langue.Language do
defstruct slug: nil, plural_forms: nil
end
3 changes: 2 additions & 1 deletion lib/langue/langue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ defmodule Langue do
SimpleJson,
Strings,
LaravelPhp,
GoI18nJson
GoI18nJson,
XLIFF12
]

for format <- @formats, module = Module.concat([Langue, Formatter, format]), name = module.name() do
Expand Down
8 changes: 5 additions & 3 deletions lib/movement/persisters/project_sync.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Movement.Persisters.ProjectSync do

import Movement.Context, only: [assign: 3]

alias Accent.{Project, Repo}
alias Accent.{Document, Project, Repo}
alias Movement.Persisters.Base, as: BasePersister

@batch_action "sync"
Expand All @@ -27,10 +27,12 @@ defmodule Movement.Persisters.ProjectSync do
end)
end

defp persist_document(context = %Movement.Context{assigns: %{document_update: nil, document: %{id: id}}}) when not is_nil(id), do: context

defp persist_document(context = %Movement.Context{assigns: %{document_update: document_update, document: document = %{id: id}}}) when not is_nil(id) do
document =
document
|> Accent.Document.changeset(document_update)
|> Document.changeset(document_update)
|> Repo.update!()

assign(context, :document, document)
Expand All @@ -41,7 +43,7 @@ defmodule Movement.Persisters.ProjectSync do
defp persist_document(context = %Movement.Context{assigns: %{document: document}}) do
document =
document
|> Accent.Document.changeset(%{})
|> Document.changeset(%{})
|> Repo.insert!()

assign(context, :document, document)
Expand Down
Loading

0 comments on commit fe69c3c

Please sign in to comment.