any2tmx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e34e73e877005effaf94729c0d3929359ee04e96
4
+ data.tar.gz: 5778d82075a5db914398b25af2c19e69885fd043
5
+ SHA512:
6
+ metadata.gz: 7e7539b3f90f17c4b94e22838ad0d9325ed2a0201a03aba27dfff39f15422ca8c4d24d9b70130e0a702a1748b4a35a500daa3b7f1b35a60fa17fe11c18538926
7
+ data.tar.gz: 6658db3c6acd69e5fe0cfbb1013becffee73e159cee8b98f7483adca03448543dfdfea8f18a609af1754ac07e28db7fe4c1da12bd52c771abd178bacc05f87e4
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rake'
7
+ gem 'pry-nav'
8
+ end
data/History.txt ADDED
@@ -0,0 +1,8 @@
1
+ == 1.0.0
2
+
3
+ * Birthday!
4
+
5
+ == 1.0.0
6
+
7
+ * Renamed gem from yaml2tmx to any2tmx.
8
+ * Added support for JSON and Android XML.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
4
+ require 'rubygems/package_task'
5
+ require 'bundler'
6
+
7
+ Bundler::GemHelper.install_tasks
data/any2tmx.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'any2tmx/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "any2tmx"
6
+ s.version = ::Any2Tmx::VERSION
7
+ s.authors = ["Cameron Dutro"]
8
+ s.email = ["camertron@gmail.com"]
9
+ s.homepage = "http://github.com/camertron/any2tmx"
10
+
11
+ s.description = s.summary = "A command-line tool to convert certain file types to the standard TMX format for translation memories."
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+ s.has_rdoc = true
15
+
16
+ s.add_dependency 'xml-write-stream', '~> 1.0'
17
+ s.add_dependency 'nokogiri', '~> 1.6'
18
+ s.add_dependency 'htmlentities', '~> 4.3'
19
+
20
+ s.executables << 'yaml2tmx'
21
+ s.executables << 'json2tmx'
22
+ s.executables << 'android2tmx'
23
+
24
+ s.require_path = 'lib'
25
+ s.files = Dir["{lib,spec}/**/*", "Gemfile", "History.txt", "README.md", "Rakefile", "any2tmx.gemspec"]
26
+ end
data/bin/android2tmx ADDED
@@ -0,0 +1,32 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'any2tmx'
4
+
5
+ options = Any2Tmx::Options.new('android2tmx')
6
+
7
+ unless options.valid?
8
+ puts options.errors.first
9
+ exit 1
10
+ end
11
+
12
+ if options.help?
13
+ options.print_help
14
+ exit 0
15
+ end
16
+
17
+ transform = Any2Tmx::Transforms::AndroidTransform.new(options)
18
+ result = transform.result
19
+
20
+ STDERR.write("Matched #{result.processed_count} of #{result.source_phrase_count} source phrases\n")
21
+
22
+ stream = if options.output
23
+ File.open(options.output, 'w+')
24
+ else
25
+ STDOUT
26
+ end
27
+
28
+ result.write(stream)
29
+
30
+ if options.output
31
+ STDERR.write("Wrote #{options.output}\n")
32
+ end
data/bin/json2tmx ADDED
@@ -0,0 +1,32 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'any2tmx'
4
+
5
+ options = Any2Tmx::Options.new('json2tmx')
6
+
7
+ unless options.valid?
8
+ puts options.errors.first
9
+ exit 1
10
+ end
11
+
12
+ if options.help?
13
+ options.print_help
14
+ exit 0
15
+ end
16
+
17
+ transform = Any2Tmx::Transforms::JsonTransform.new(options)
18
+ result = transform.result
19
+
20
+ STDERR.write("Matched #{result.processed_count} of #{result.source_phrase_count} source phrases\n")
21
+
22
+ stream = if options.output
23
+ File.open(options.output, 'w+')
24
+ else
25
+ STDOUT
26
+ end
27
+
28
+ result.write(stream)
29
+
30
+ if options.output
31
+ STDERR.write("Wrote #{options.output}\n")
32
+ end
data/bin/yaml2tmx ADDED
@@ -0,0 +1,32 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'any2tmx'
4
+
5
+ options = Any2Tmx::Options.new('yaml2tmx')
6
+
7
+ unless options.valid?
8
+ puts options.errors.first
9
+ exit 1
10
+ end
11
+
12
+ if options.help?
13
+ options.print_help
14
+ exit 0
15
+ end
16
+
17
+ transform = Any2Tmx::Transforms::YamlTransform.new(options)
18
+ result = transform.result
19
+
20
+ STDERR.write("Matched #{result.processed_count} of #{result.source_phrase_count} source phrases\n")
21
+
22
+ stream = if options.output
23
+ File.open(options.output, 'w+')
24
+ else
25
+ STDOUT
26
+ end
27
+
28
+ result.write(stream)
29
+
30
+ if options.output
31
+ STDERR.write("Wrote #{options.output}\n")
32
+ end
@@ -0,0 +1,146 @@
1
+ require 'nokogiri'
2
+ require 'htmlentities'
3
+
4
+ # These classes have been adapted from:
5
+ # https://github.com/rosette-proj/rosette-extractor-xml
6
+
7
+ class HTMLEntities
8
+ MAPPINGS['android_xml'] = MAPPINGS['xhtml1'].dup.tap do |mappings|
9
+ mappings.delete('apos')
10
+ end
11
+
12
+ FLAVORS << 'android_xml'
13
+
14
+ class AndroidXmlDecoder < Decoder
15
+ def initialize
16
+ super('android_xml')
17
+ end
18
+ end
19
+ end
20
+
21
+ module Any2Tmx
22
+ class AndroidXmlParser
23
+ class << self
24
+ def parse(xml_content)
25
+ doc = parse_xml(xml_content)
26
+ result = {}.tap do |result|
27
+ collector = lambda { |text, key| result[key] = text }
28
+ each_string_entry(doc, &collector)
29
+ each_array_entry(doc, &collector)
30
+ each_plural_entry(doc, &collector)
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def parse_xml(xml_content)
37
+ Nokogiri::XML(xml_content) do |config|
38
+ config.options = Nokogiri::XML::ParseOptions::NONET
39
+ end
40
+ end
41
+
42
+ def each_string_entry(doc)
43
+ doc.xpath('//string').each do |node|
44
+ yield(
45
+ text_from(node),
46
+ name_from(node)
47
+ )
48
+ end
49
+ end
50
+
51
+ def each_array_entry(doc)
52
+ doc.xpath('//string-array').each do |array|
53
+ prefix = name_from(array)
54
+
55
+ array.xpath('item').each_with_index do |item, idx|
56
+ yield(
57
+ text_from(item),
58
+ "#{prefix}.#{idx}"
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ def each_plural_entry(doc)
65
+ doc.xpath('//plurals').each do |plurals|
66
+ prefix = name_from(plurals)
67
+
68
+ plurals.xpath('item').each do |item|
69
+ quantity = item.attributes['quantity'].value
70
+
71
+ yield(
72
+ text_from(item),
73
+ "#{prefix}.#{quantity}"
74
+ )
75
+ end
76
+ end
77
+ end
78
+
79
+ def text_from(node)
80
+ builder = Nokogiri::XML::Builder.new do |builder|
81
+ builder.root do
82
+ node.children.each do |child|
83
+ serialize(child, builder)
84
+ end
85
+ end
86
+ end
87
+
88
+ # safe to call `strip` after `to_xml` because any string that
89
+ # needs leading or trailing whitespace preserved should be wrapped
90
+ # in double quotes
91
+ unescape(
92
+ strip_enclosing_quotes(
93
+ builder.doc.xpath('/root/node()').to_xml.strip
94
+ )
95
+ )
96
+ end
97
+
98
+ def serialize(node, builder)
99
+ if node.text?
100
+ builder.text(unescape(node.text))
101
+ else
102
+ builder.send("#{node.name}_", node.attributes) do
103
+ node.children.each do |child|
104
+ serialize(child, builder)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def name_from(node)
111
+ if attribute = node.attributes['name']
112
+ attribute.value
113
+ end
114
+ end
115
+
116
+ def unescape(text)
117
+ text = text
118
+ .gsub("\\'", "'")
119
+ .gsub('\\"', '"')
120
+ .gsub("\\n", "\n")
121
+ .gsub("\\r", "\r")
122
+ .gsub("\\t", "\t")
123
+
124
+ coder.decode(text)
125
+ end
126
+
127
+ def coder
128
+ @coder ||= HTMLEntities::AndroidXmlDecoder.new
129
+ end
130
+
131
+ def strip_enclosing_quotes(text)
132
+ quote = case text[0]
133
+ when "'", '"'
134
+ text[0]
135
+ end
136
+
137
+ if quote
138
+ text.gsub(/\A#{quote}(.*)#{quote}\z/) { $1 }
139
+ else
140
+ text
141
+ end
142
+ end
143
+
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,120 @@
1
+ require 'optparse'
2
+
3
+ module Any2Tmx
4
+ class Options
5
+ attr_reader :errors
6
+
7
+ def initialize(executable_name)
8
+ @options = {}
9
+ @errors = []
10
+
11
+ OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{executable_name} [options]"
13
+
14
+ opts.on('-s', '--source [file]:[locale]', 'File containing phrases in the source locale (locale appended with colon).') do |source|
15
+ file, locale = source.split(':')
16
+ @options[:source] = file
17
+ @options[:source_locale] = locale
18
+ end
19
+
20
+ opts.on('-t', '--target [file]:[locale]', 'File containing translations in the target locale (locale appended with colon).') do |target|
21
+ file, locale = target.split(':')
22
+ @options[:target] = file
23
+ @options[:target_locale] = locale
24
+ end
25
+
26
+ opts.on('-o', '--output [file]', 'The TMX output file to write. If not specified, output is printed to stdout.') do |output|
27
+ @options[:output] = output
28
+ end
29
+
30
+ opts.on('-h', '--help', 'Prints this help message.') do
31
+ @options[:help] = true
32
+ end
33
+
34
+ @opts = opts
35
+
36
+ # give derived classes the opportunity to add additional options
37
+ yield opts if block_given?
38
+ end.parse!
39
+ end
40
+
41
+ def valid?
42
+ errors.clear
43
+ validate
44
+ errors.empty?
45
+ end
46
+
47
+ def source
48
+ @options[:source]
49
+ end
50
+
51
+ def target
52
+ @options[:target]
53
+ end
54
+
55
+ def source_locale
56
+ @options[:source_locale]
57
+ end
58
+
59
+ def target_locale
60
+ @options[:target_locale]
61
+ end
62
+
63
+ def output
64
+ @options[:output]
65
+ end
66
+
67
+ def help?
68
+ @options[:help]
69
+ end
70
+
71
+ def print_help
72
+ puts @opts
73
+ end
74
+
75
+ # allow derived classes to add additional validation routines
76
+ def before_validate
77
+ end
78
+
79
+ def after_validate
80
+ end
81
+
82
+ private
83
+
84
+ def validate
85
+ unless help?
86
+ before_validate
87
+ validate_source
88
+ validate_target
89
+ validate_output
90
+ after_validate
91
+ end
92
+ end
93
+
94
+ def validate_source
95
+ unless File.exist?(source)
96
+ errors << 'Source file does not exist.'
97
+ end
98
+
99
+ unless source_locale
100
+ errors << 'Source locale not provided. Try running with the -h option for usage details.'
101
+ end
102
+ end
103
+
104
+ def validate_target
105
+ unless File.exist?(target)
106
+ errors << 'Target file does not exist.'
107
+ end
108
+
109
+ unless target_locale
110
+ errors << 'Target locale not provided. Try running with the -h option for usage details.'
111
+ end
112
+ end
113
+
114
+ def validate_output
115
+ unless File.exist?(File.dirname(target))
116
+ errors << 'Output path does not exist.'
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,14 @@
1
+ module Any2Tmx
2
+ class PhraseSet
3
+ attr_reader :traversable, :locale
4
+
5
+ def initialize(traversable, locale)
6
+ @traversable = traversable
7
+ @locale = locale
8
+ end
9
+
10
+ def zip(other_set, &block)
11
+ traversable.zip(other_set.traversable, &block)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ require 'xml-write-stream'
2
+
3
+ module Any2Tmx
4
+ class TmxWriter
5
+ class << self
6
+ def write(trans_map, source_locale, target_locale, io)
7
+ writer = XmlWriteStream.from_stream(io)
8
+ writer.open_tag('tmx', version: '1.4')
9
+ writer.open_single_line_tag('header', srclang: source_locale, datatype: 'plaintext', segtype: 'paragraph')
10
+ writer.close_tag
11
+
12
+ writer.open_tag('body')
13
+
14
+ trans_map.each_pair do |source_phrase, target_phrase|
15
+ writer.open_tag('tu')
16
+ writer.open_tag('tuv', 'xml:lang' => source_locale)
17
+ writer.open_single_line_tag('seg')
18
+ writer.write_text(source_phrase)
19
+ writer.close_tag # seg
20
+ writer.close_tag # tuv
21
+
22
+ writer.open_tag('tuv', 'xml:lang' => target_locale)
23
+ writer.open_single_line_tag('seg')
24
+ writer.write_text(target_phrase)
25
+ writer.close_tag # seg
26
+ writer.close_tag # tuv
27
+ writer.close_tag # tu
28
+ end
29
+
30
+ writer.close
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ module Any2Tmx
2
+ module Transforms
3
+ class AndroidTransform < Transform
4
+ private
5
+
6
+ def load(file, locale)
7
+ phrases = Any2Tmx::AndroidXmlParser.parse(File.read(file))
8
+ traversable = Any2Tmx::Traversable.new(phrases)
9
+ Any2Tmx::PhraseSet.new(traversable, locale)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Any2Tmx
4
+ module Transforms
5
+ class JsonTransform < Transform
6
+ private
7
+
8
+ def load(file, locale)
9
+ phrases = JSON.parse(File.read(file))
10
+ traversable = Any2Tmx::Traversable.new(phrases)
11
+ Any2Tmx::PhraseSet.new(traversable, locale)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module Any2Tmx
2
+ module Transforms
3
+ class Result
4
+ attr_reader :source, :target, :collection, :processed_count
5
+
6
+ def initialize(source, target, collection, processed_count)
7
+ @source = source
8
+ @target = target
9
+ @collection = collection
10
+ @processed_count = processed_count
11
+ end
12
+
13
+ def source_phrase_count
14
+ source.traversable.size
15
+ end
16
+
17
+ def write(io)
18
+ Any2Tmx::TmxWriter.write(
19
+ collection, source.locale, target.locale, io
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Any2Tmx
2
+ module Transforms
3
+ class Transform
4
+ attr_reader :options
5
+
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+
10
+ def result
11
+ source = load(options.source, options.source_locale)
12
+ target = load(options.target, options.target_locale)
13
+ count = 0
14
+ zipped = source.zip(target) { count += 1 }
15
+ Result.new(source, target, zipped, count)
16
+ end
17
+
18
+ private
19
+
20
+ def load(file, locale)
21
+ raise NotImplementedError,
22
+ "#{__method__} must be defined in derived classes"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ require 'yaml'
2
+
3
+ module Any2Tmx
4
+ module Transforms
5
+ class YamlTransform < Transform
6
+ private
7
+
8
+ def load(file, locale)
9
+ phrases = YAML.load_file(file)
10
+
11
+ if phrases.include?(locale)
12
+ phrases = phrases[locale]
13
+ end
14
+
15
+ traversable = Any2Tmx::Traversable.new(phrases)
16
+ Any2Tmx::PhraseSet.new(traversable, locale)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Any2Tmx
2
+ module Transforms
3
+ autoload :AndroidTransform, 'any2tmx/transforms/android_transform'
4
+ autoload :JsonTransform, 'any2tmx/transforms/json_transform'
5
+ autoload :Transform, 'any2tmx/transforms/transform'
6
+ autoload :Result, 'any2tmx/transforms/result'
7
+ autoload :YamlTransform, 'any2tmx/transforms/yaml_transform'
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ module Any2Tmx
2
+ class Traversable
3
+ attr_reader :collection
4
+
5
+ def initialize(collection)
6
+ @collection = collection
7
+ end
8
+
9
+ def each_entry(&block)
10
+ if block_given?
11
+ each_entry_helper(collection, [], &block)
12
+ else
13
+ to_enum(__method__)
14
+ end
15
+ end
16
+
17
+ def size
18
+ each_entry.inject(0) { |ret, _| ret + 1 }
19
+ end
20
+
21
+ def dig(path)
22
+ path.inject(:start) do |ret, seg|
23
+ if ret == :start
24
+ collection[seg]
25
+ elsif ret
26
+ if seg.is_a?(Numeric) && ret.is_a?(Array)
27
+ ret[seg] # array index case
28
+ elsif seg.is_a?(String) && ret.is_a?(Hash)
29
+ ret[seg] # hash key case
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def zip(other_traversable)
36
+ each_entry.each_with_object({}) do |(entry, path), ret|
37
+ other_entry = other_traversable.dig(path)
38
+ ret[entry] = other_entry if other_entry
39
+ yield if block_given? && other_entry
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def each_entry_helper(coll, path, &block)
46
+ case coll
47
+ when Hash
48
+ coll.each_pair do |key, value|
49
+ each_entry_helper(value, path + [key], &block)
50
+ end
51
+
52
+ when Array
53
+ coll.each_with_index do |element, idx|
54
+ each_entry_helper(element, path + [idx], &block)
55
+ end
56
+
57
+ when String
58
+ yield coll, path
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module Any2Tmx
2
+ VERSION = '1.0.0'
3
+ end
data/lib/any2tmx.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Any2Tmx
2
+ autoload :AndroidXmlParser, 'any2tmx/android_xml_parser'
3
+ autoload :Options, 'any2tmx/options'
4
+ autoload :PhraseSet, 'any2tmx/phrase_set'
5
+ autoload :TmxWriter, 'any2tmx/tmx_writer'
6
+ autoload :Transforms, 'any2tmx/transforms'
7
+ autoload :Traversable, 'any2tmx/traversable'
8
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: any2tmx
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Dutro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xml-write-stream
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: htmlentities
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.3'
55
+ description: A command-line tool to convert certain file types to the standard TMX
56
+ format for translation memories.
57
+ email:
58
+ - camertron@gmail.com
59
+ executables:
60
+ - yaml2tmx
61
+ - json2tmx
62
+ - android2tmx
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - Gemfile
67
+ - History.txt
68
+ - Rakefile
69
+ - any2tmx.gemspec
70
+ - bin/android2tmx
71
+ - bin/json2tmx
72
+ - bin/yaml2tmx
73
+ - lib/any2tmx.rb
74
+ - lib/any2tmx/android_xml_parser.rb
75
+ - lib/any2tmx/options.rb
76
+ - lib/any2tmx/phrase_set.rb
77
+ - lib/any2tmx/tmx_writer.rb
78
+ - lib/any2tmx/transforms.rb
79
+ - lib/any2tmx/transforms/android_transform.rb
80
+ - lib/any2tmx/transforms/json_transform.rb
81
+ - lib/any2tmx/transforms/result.rb
82
+ - lib/any2tmx/transforms/transform.rb
83
+ - lib/any2tmx/transforms/yaml_transform.rb
84
+ - lib/any2tmx/traversable.rb
85
+ - lib/any2tmx/version.rb
86
+ homepage: http://github.com/camertron/any2tmx
87
+ licenses: []
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.2.3
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: A command-line tool to convert certain file types to the standard TMX format
109
+ for translation memories.
110
+ test_files: []
111
+ has_rdoc: true