xliff 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab1e4614db50aed2434bfb0dd40806e26d7316df9267ddc0214b53ac6ad282a4
4
+ data.tar.gz: fb97a9d1e8a95b06a625ca35f6d123c7438ca635b73e4f3df223655aeaa06170
5
+ SHA512:
6
+ metadata.gz: 25adf170edb6cf6810feed9399adb423320a87a8f9ed01c7564e836b6a9b274c450f1c02f9c14ccdd78d1035c7f9cd2b44b1cfb24ce8299e1f5e4e6c34feb2e1
7
+ data.tar.gz: aac73d5358320549c165b6e6bbd0f7224a5e1215575c4de3ab120e45fd4483cd1cc6b47077f575c7bacdc3121ebbd881cdaed5b7d6aafdc12f2f12d80132052e
@@ -0,0 +1,95 @@
1
+ # Nodes with values to reuse in the pipeline.
2
+ common_params:
3
+ plugins:
4
+ - &docker_plugin
5
+ docker#v3.8.0:
6
+ image: &ruby_version "ruby:2.7.4"
7
+ propagate-environment: true
8
+ environment:
9
+ - "RUBYGEMS_API_KEY"
10
+ - &docker_plugin_with_danger_token
11
+ docker#v3.8.0:
12
+ image: *ruby_version
13
+ propagate-environment: true
14
+ environment:
15
+ - "DANGER_GITHUB_API_TOKEN"
16
+
17
+ steps:
18
+ #################
19
+ # Build and Test
20
+ #################
21
+ - label: "🧪 Build and Test"
22
+ key: test
23
+ command: |
24
+ bundle install
25
+
26
+ echo "--- :rubocop: Run Tests"
27
+ bundle exec rspec
28
+ plugins: [*docker_plugin]
29
+
30
+ #################
31
+ # Lint (Code)
32
+ #################
33
+ - label: "🧹 Lint (Rubocop)"
34
+ key: rubocop
35
+ command: |
36
+ bundle install
37
+
38
+ echo "--- :rubocop: Run Rubocop"
39
+ bundle exec rubocop
40
+ plugins: [*docker_plugin]
41
+
42
+ #################
43
+ # Lint (Documentation)
44
+ #################
45
+ - label: "🧹 Lint (Yardstick)"
46
+ key: yardstick
47
+ command: |
48
+ bundle install
49
+ bundle exec rake yardstick_measure
50
+ bundle exec rake verify_measurements
51
+ plugins: [*docker_plugin]
52
+
53
+ #################
54
+ # Check Lockfile
55
+ #################
56
+ - label: "🧹 Check Lockfile"
57
+ key: lockfile
58
+ command: |
59
+ bundle install
60
+ bundle check
61
+ plugins: [*docker_plugin]
62
+
63
+ #################
64
+ # Danger
65
+ #################
66
+ - label: "⛔️ Danger"
67
+ key: danger
68
+ command: |
69
+ bundle install
70
+
71
+ echo "--- :rspec: Generate Code Coverage Data"
72
+ bundle exec rspec
73
+
74
+ echo "--- :warning: Run Danger"
75
+ bundle exec danger
76
+ plugins: [*docker_plugin_with_danger_token]
77
+
78
+ #################
79
+ # Push to RubyGems
80
+ #################
81
+ - label: ":rubygems: Publish to RubyGems"
82
+ key: "gem-push"
83
+ if: build.tag != null
84
+ depends_on:
85
+ - test
86
+ - rubocop
87
+ - danger
88
+ - lockfile
89
+ # Note: We intentionally call a separate `.sh` script here (as opposed to having all the
90
+ # commands written inline) to avoid leaking a key used in the process in clear in the
91
+ # BUILDKITE_COMMAND environment variable.
92
+ command: .buildkite/gem-push.sh
93
+ plugins: [*docker_plugin]
94
+ agents:
95
+ queue: "default"
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_PATH: "vendor/bundle"
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7.4
3
+ NewCops: enable
4
+
5
+ # Don't fail for having too many tests
6
+ Metrics/BlockLength:
7
+ AllowedMethods: ['describe']
8
+
9
+ require:
10
+ - rubocop-rspec
11
+ - rubocop-rake
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-04-23
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at 1123407+jkmassel@users.noreply.github.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Dangerfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Report Rubocop Violations
4
+ github.dismiss_out_of_range_messages
5
+ rubocop.lint(
6
+ inline_comment: true,
7
+ fail_on_inline_comment: true,
8
+ report_danger: true
9
+ )
10
+
11
+ # Report Test Coverage
12
+ simplecov.individual_report('coverage/coverage.json', Dir.pwd)
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in xliff.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rspec', '~> 3.0'
10
+
11
+ gem 'rubocop', '~> 1.21'
12
+ gem 'rubocop-rake', '~> 0.6.0'
13
+ gem 'rubocop-rspec', '~> 2.10'
14
+
15
+ gem 'simplecov', '~> 0.21.2'
16
+ gem 'simplecov-json', '~> 0.2'
17
+
18
+ gem 'danger', '~> 8.6'
19
+ gem 'danger-rubocop', '~> 0.10'
20
+ gem 'danger-simplecov_json', '~> 0.3'
21
+
22
+ gem 'yard', '~> 0.9.27'
23
+ gem 'yardstick', '~> 0.9.9'
data/Gemfile.lock ADDED
@@ -0,0 +1,177 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xliff (1.0.0)
5
+ nokogiri (~> 1.13)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.8.6)
11
+ public_suffix (>= 2.0.2, < 6.0)
12
+ ast (2.4.2)
13
+ claide (1.1.0)
14
+ claide-plugins (0.9.2)
15
+ cork
16
+ nap
17
+ open4 (~> 1.3)
18
+ colored2 (3.1.2)
19
+ cork (0.3.0)
20
+ colored2 (~> 3.1)
21
+ danger (8.6.1)
22
+ claide (~> 1.0)
23
+ claide-plugins (>= 0.9.2)
24
+ colored2 (~> 3.1)
25
+ cork (~> 0.1)
26
+ faraday (>= 0.9.0, < 2.0)
27
+ faraday-http-cache (~> 2.0)
28
+ git (~> 1.7)
29
+ kramdown (~> 2.3)
30
+ kramdown-parser-gfm (~> 1.0)
31
+ no_proxy_fix
32
+ octokit (~> 4.7)
33
+ terminal-table (>= 1, < 4)
34
+ danger-plugin-api (1.0.0)
35
+ danger (> 2.0)
36
+ danger-rubocop (0.12.0)
37
+ danger
38
+ rubocop (~> 1.0)
39
+ danger-simplecov_json (0.3.0)
40
+ danger-plugin-api (~> 1.0)
41
+ diff-lcs (1.5.0)
42
+ docile (1.4.0)
43
+ faraday (1.10.3)
44
+ faraday-em_http (~> 1.0)
45
+ faraday-em_synchrony (~> 1.0)
46
+ faraday-excon (~> 1.1)
47
+ faraday-httpclient (~> 1.0)
48
+ faraday-multipart (~> 1.0)
49
+ faraday-net_http (~> 1.0)
50
+ faraday-net_http_persistent (~> 1.0)
51
+ faraday-patron (~> 1.0)
52
+ faraday-rack (~> 1.0)
53
+ faraday-retry (~> 1.0)
54
+ ruby2_keywords (>= 0.0.4)
55
+ faraday-em_http (1.0.0)
56
+ faraday-em_synchrony (1.0.0)
57
+ faraday-excon (1.1.0)
58
+ faraday-http-cache (2.5.1)
59
+ faraday (>= 0.8)
60
+ faraday-httpclient (1.0.1)
61
+ faraday-multipart (1.0.4)
62
+ multipart-post (~> 2)
63
+ faraday-net_http (1.0.1)
64
+ faraday-net_http_persistent (1.2.0)
65
+ faraday-patron (1.0.0)
66
+ faraday-rack (1.0.0)
67
+ faraday-retry (1.0.3)
68
+ git (1.19.1)
69
+ addressable (~> 2.8)
70
+ rchardet (~> 1.8)
71
+ json (2.7.1)
72
+ kramdown (2.4.0)
73
+ rexml
74
+ kramdown-parser-gfm (1.1.0)
75
+ kramdown (~> 2.0)
76
+ language_server-protocol (3.17.0.3)
77
+ mini_portile2 (2.8.5)
78
+ multipart-post (2.3.0)
79
+ nap (1.1.0)
80
+ no_proxy_fix (0.1.2)
81
+ nokogiri (1.15.5)
82
+ mini_portile2 (~> 2.8.2)
83
+ racc (~> 1.4)
84
+ octokit (4.25.1)
85
+ faraday (>= 1, < 3)
86
+ sawyer (~> 0.9)
87
+ open4 (1.3.4)
88
+ parallel (1.24.0)
89
+ parser (3.3.0.5)
90
+ ast (~> 2.4.1)
91
+ racc
92
+ public_suffix (5.0.4)
93
+ racc (1.7.3)
94
+ rainbow (3.1.1)
95
+ rake (13.1.0)
96
+ rchardet (1.8.0)
97
+ regexp_parser (2.9.0)
98
+ rexml (3.2.6)
99
+ rspec (3.12.0)
100
+ rspec-core (~> 3.12.0)
101
+ rspec-expectations (~> 3.12.0)
102
+ rspec-mocks (~> 3.12.0)
103
+ rspec-core (3.12.2)
104
+ rspec-support (~> 3.12.0)
105
+ rspec-expectations (3.12.3)
106
+ diff-lcs (>= 1.2.0, < 2.0)
107
+ rspec-support (~> 3.12.0)
108
+ rspec-mocks (3.12.6)
109
+ diff-lcs (>= 1.2.0, < 2.0)
110
+ rspec-support (~> 3.12.0)
111
+ rspec-support (3.12.1)
112
+ rubocop (1.60.2)
113
+ json (~> 2.3)
114
+ language_server-protocol (>= 3.17.0)
115
+ parallel (~> 1.10)
116
+ parser (>= 3.3.0.2)
117
+ rainbow (>= 2.2.2, < 4.0)
118
+ regexp_parser (>= 1.8, < 3.0)
119
+ rexml (>= 3.2.5, < 4.0)
120
+ rubocop-ast (>= 1.30.0, < 2.0)
121
+ ruby-progressbar (~> 1.7)
122
+ unicode-display_width (>= 2.4.0, < 3.0)
123
+ rubocop-ast (1.30.0)
124
+ parser (>= 3.2.1.0)
125
+ rubocop-capybara (2.20.0)
126
+ rubocop (~> 1.41)
127
+ rubocop-factory_bot (2.25.1)
128
+ rubocop (~> 1.41)
129
+ rubocop-rake (0.6.0)
130
+ rubocop (~> 1.0)
131
+ rubocop-rspec (2.26.1)
132
+ rubocop (~> 1.40)
133
+ rubocop-capybara (~> 2.17)
134
+ rubocop-factory_bot (~> 2.22)
135
+ ruby-progressbar (1.13.0)
136
+ ruby2_keywords (0.0.5)
137
+ sawyer (0.9.2)
138
+ addressable (>= 2.3.5)
139
+ faraday (>= 0.17.3, < 3)
140
+ simplecov (0.21.2)
141
+ docile (~> 1.1)
142
+ simplecov-html (~> 0.11)
143
+ simplecov_json_formatter (~> 0.1)
144
+ simplecov-html (0.12.3)
145
+ simplecov-json (0.2.3)
146
+ json
147
+ simplecov
148
+ simplecov_json_formatter (0.1.4)
149
+ terminal-table (3.0.2)
150
+ unicode-display_width (>= 1.1.1, < 3)
151
+ unicode-display_width (2.5.0)
152
+ yard (0.9.34)
153
+ yardstick (0.9.9)
154
+ yard (~> 0.8, >= 0.8.7.2)
155
+
156
+ PLATFORMS
157
+ ruby
158
+ x86_64-darwin-19
159
+ x86_64-darwin-20
160
+
161
+ DEPENDENCIES
162
+ danger (~> 8.6)
163
+ danger-rubocop (~> 0.10)
164
+ danger-simplecov_json (~> 0.3)
165
+ rake (~> 13.0)
166
+ rspec (~> 3.0)
167
+ rubocop (~> 1.21)
168
+ rubocop-rake (~> 0.6.0)
169
+ rubocop-rspec (~> 2.10)
170
+ simplecov (~> 0.21.2)
171
+ simplecov-json (~> 0.2)
172
+ xliff!
173
+ yard (~> 0.9.27)
174
+ yardstick (~> 0.9.9)
175
+
176
+ BUNDLED WITH
177
+ 2.3.9
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Jeremy Massel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,15 @@
1
+ test:
2
+ docker run -v $(shell pwd):/app -w /app public.ecr.aws/docker/library/ruby:2.7.4-bullseye /bin/bash -c "bundle install && bundle exec rspec"
3
+
4
+ lint:
5
+ docker run -v $(shell pwd):/app -w /app public.ecr.aws/docker/library/ruby:2.7.4-bullseye /bin/bash -c "bundle install && bundle exec rubocop"
6
+
7
+ update-dependencies:
8
+ docker run -v $(shell pwd):/app -w /app public.ecr.aws/docker/library/ruby:2.7.4-bullseye /bin/bash -c "bundle update"
9
+
10
+ bundle-check:
11
+ docker run -v $(shell pwd):/app -w /app public.ecr.aws/docker/library/ruby:2.7.4-bullseye /bin/bash -c "bundle install && bundle check"
12
+
13
+ build:
14
+ docker run -v $(shell pwd):/app -w /app public.ecr.aws/docker/library/ruby:2.7.4-bullseye /bin/bash -c "gem build xliff.gemspec"
15
+
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Xliff
2
+
3
+ This gem is for parsing and building `xliff` files.
4
+
5
+ ## Usage
6
+
7
+ The gem is meant to handle two tasks – reading `xliff` files and creating new ones.
8
+
9
+ ### Reading `xliff` files
10
+
11
+ ```ruby
12
+ bundle = Xliff::Bundle.from_path('path/to/my/file.xliff')
13
+ bundle.files.each do |file|
14
+ puts "File: #{file.original}:"
15
+ file.entries.each do |entry|
16
+ puts "#{entry.source}:#{entry.target}"
17
+ end
18
+ end
19
+ ```
20
+
21
+ ### Creating `xliff` files
22
+ ```ruby
23
+
24
+ bundle = Xliff::Bundle.new(path: 'path/to/my/file.xliff')
25
+ file = Xliff::File.new(original: 'info.plist', source_language: 'en', target_language: 'fr')
26
+ entry = Xliff::Entry.new(id: 1234, source: 'hello', target: 'bounjour')
27
+ file.add_entry(entry)
28
+ bundle.add_file(file)
29
+
30
+ xml = bundle.to_s
31
+ ```
32
+
33
+ In the above example, `xml` reads:
34
+
35
+ ```xml
36
+ <xliff>
37
+ <file original="info.plist" source-language="en" target-language="fr" datatype="plaintext">
38
+ <body>
39
+ <trans-unit id="1234" xml:space="default">
40
+ <source>hello</source>
41
+ <target>bounjour</target>
42
+ </trans-unit>
43
+ </body>
44
+ </file>
45
+ </xliff>
46
+ ```
47
+
48
+ ## Development
49
+
50
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bundle exec console` for an interactive prompt that will allow you to experiment.
51
+
52
+ ## Contributing
53
+
54
+ Bug reports and pull requests are welcome on GitHub at https://github.com/automattic/xliff. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/automattic/xliff/blob/trunk/CODE_OF_CONDUCT.md).
55
+
56
+ ## License
57
+
58
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
59
+
60
+ ## Code of Conduct
61
+
62
+ Everyone interacting in the Xliff project's codebase and issue tracker is expected to follow the [code of conduct](https://github.com/automattic/xliff/blob/trunk/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ abort 'Please run rake using `bundle exec`' unless %w[BUNDLE_BIN_PATH BUNDLE_GEMFILE].any? { |k| ENV.key?(k) }
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ require 'rubocop/rake_task'
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %i[rubocop:auto_correct spec]
15
+
16
+ ## Documentation Coverage
17
+ require 'yardstick/rake/measurement'
18
+ require 'yardstick/rake/verify'
19
+
20
+ Yardstick::Rake::Measurement.new(:yardstick_measure) do |measurement|
21
+ measurement.output = 'coverage/yard-coverage.txt'
22
+ end
23
+
24
+ Yardstick::Rake::Verify.new do |verify|
25
+ verify.threshold = 91.9
26
+ end
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # require 'bundler/setup'
5
+ require 'xliff'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module Xliff
6
+ # Models a collection of files for translation
7
+ class Bundle
8
+ # An array of translated files in this bundle
9
+ # @return [Array<File>]
10
+ # @api public
11
+ # @example Retrieve the bundle's files
12
+ # "bundle.files" #=> [{File}]
13
+ attr_reader :files
14
+
15
+ # The path on disk that this bundle was read from
16
+ # @!attribute [rw] path
17
+ # @return [String]
18
+ # @api public
19
+ # @example Retrieve the bundle path
20
+ # "bundle.path" #=> /tmp/foo.xliff
21
+ attr_accessor :path
22
+
23
+ # Create a blank {Bundle} object, suitable for building an XLIFF file by hand
24
+ #
25
+ # @param [String] path An optional path to where the file should be stored on disk.
26
+ # @example Create an empty XLIFF bundle
27
+ # bundle.new
28
+ # @example Create an empty XLIFF bundle with a pre-specified path
29
+ # bundle.new(path: /path/to/my/output/file.xliff)
30
+ def initialize(path: nil)
31
+ @path = path
32
+ @files = []
33
+ end
34
+
35
+ # Add an additional {File} object to the bundle
36
+ #
37
+ # @param [File] file The file to be stored in the bundle.
38
+ # @example Add a new file to the bundle
39
+ # file = File.new(...)
40
+ # bundle.add_file(file)
41
+ # @return
42
+ def add_file(file)
43
+ @files << file
44
+ end
45
+
46
+ # Find a given file by name
47
+ #
48
+ # If two files exist with the same name, only the first will be returned.
49
+ #
50
+ # @param [String] name The name of the file to locate. If found it is returned.
51
+ # @example Look up an existing file
52
+ # # Bundle contains two files: [foo.txt, bar.txt]
53
+ # bundle.file_named('foo.txt') => {File}
54
+ # @example Look up a non-existent file
55
+ # # Bundle contains two files: [foo.txt, bar.txt]
56
+ # bundle.file_named('baz.txt') => nil
57
+ # @return [File, nil] The file, if found.
58
+ def file_named(name)
59
+ @files.find do |file|
60
+ file.original == name || ::File.basename(f.original) == name
61
+ end
62
+ end
63
+
64
+ # Encode this {Bundle} object as an XLIFF document
65
+ #
66
+ # @return [Nokogiri::XML::Document]
67
+ def to_xml
68
+ document = Nokogiri::XML::Document.new
69
+ document.encoding = 'UTF-8'
70
+
71
+ xliff_node = document.create_element('xliff')
72
+ attach_xliff_metadata(xliff_node)
73
+
74
+ return document if @files.empty?
75
+
76
+ @files.each do |file|
77
+ xliff_node.add_child(file.to_xml)
78
+ end
79
+
80
+ document.add_child(xliff_node)
81
+
82
+ document
83
+ end
84
+
85
+ # Encode this {Bundle} object as an XLIFF document string
86
+ #
87
+ # @return [String]
88
+ def to_s
89
+ to_xml.to_s.strip
90
+ end
91
+
92
+ # Parse the XLIFF file at the given `path` as an XLIFF {Bundle} object
93
+ #
94
+ # Raises for invalid input
95
+ #
96
+ # @param [String] path The path to an `xliff` file.
97
+ # @return [Bundle, nil]
98
+ def self.from_path(path)
99
+ xml = Nokogiri::XML(::File.open(path))
100
+ bundle = from_xml(xml)
101
+ bundle.path = path
102
+
103
+ bundle
104
+ end
105
+
106
+ # Parse the XLIFF file stored in the given `string` as an XLIFF {Bundle} object
107
+ #
108
+ # Raises for invalid input
109
+ #
110
+ # @param [String] string A string containing XLIFF data.
111
+ # @return [Xliff::Bundle, nil]
112
+ def self.from_string(string)
113
+ xml = Nokogiri::XML(string)
114
+ from_xml(xml)
115
+ end
116
+
117
+ # Parse the Nokogiri XML representation of an XLIFF file to a {Bundle} object
118
+ #
119
+ # Raises for invalid input
120
+ #
121
+ # @param [Nokogiri::XML::Element] xml A Nokogiri XML document containing XLIFF data.
122
+ # @return [Bundle, nil]
123
+ def self.from_xml(xml)
124
+ raise if xml.nil?
125
+
126
+ raise 'Invalid XLIFF file – the root node must be `<xliff>`' if xml.document.root.name != 'xliff'
127
+
128
+ bundle = Bundle.new
129
+
130
+ xml.document.root.element_children
131
+ .select { |node| node.name == 'file' }
132
+ .each { |node| bundle.add_file File.from_xml(node) }
133
+
134
+ bundle
135
+ end
136
+
137
+ private
138
+
139
+ # Attach the required XLIFF metadata to the given `node`
140
+ #
141
+ # Currently only supports XLIFF 1.2.
142
+ # @api private
143
+ # @return [void]
144
+ def attach_xliff_metadata(node)
145
+ node['xmlns'] = 'urn:oasis:names:tc:xliff:document:1.2'
146
+ node['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
147
+ node['version'] = '1.2'
148
+ node['xsi:schemaLocation'] = 'urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd'
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module Xliff
6
+ # Models a single translation string
7
+ class Entry
8
+ # A unique identifier for this translation string
9
+ #
10
+ # This will often match the source language string, but can also be used for cases where the
11
+ # source translation is not a suitable unique identifier.
12
+ #
13
+ # @return [String]
14
+ attr_accessor :id
15
+
16
+ # The original text
17
+ # @return [String]
18
+ attr_accessor :source
19
+
20
+ # The translated text
21
+ # @return [String]
22
+ attr_accessor :target
23
+
24
+ # Documentation for translators understand the context of a string
25
+ # @return [String]
26
+ attr_accessor :note
27
+
28
+ # The XML whitespace processing behaviour
29
+ # @return [String]
30
+ attr_accessor :xml_space
31
+
32
+ # Create a blank Entry object
33
+ #
34
+ # Most often used to build an XLIFF file by hand.
35
+ #
36
+ # @param [String] id A unique identifier for this string.
37
+ # @param [String] source The original text.
38
+ # @param [String] target The translated text.
39
+ # @param [String] note Documentation for translators understand the context of a string.
40
+ # @param [String] xml_space The XML whitespace processing behaviour.
41
+ def initialize(id:, source:, target:, note: nil, xml_space: 'default')
42
+ @id = id
43
+ @source = source
44
+ @target = target
45
+ @note = note
46
+ @xml_space = xml_space
47
+ end
48
+
49
+ # Encode this `Entry` object to an Nokogiri XML Element Representation of a `<trans-unit>` element
50
+ #
51
+ # @return [Nokogiri::XML::Element]
52
+ def to_xml
53
+ fragment = Nokogiri::XML.fragment('<trans-unit />')
54
+ trans_unit_node = fragment.at('trans-unit')
55
+ trans_unit_node['id'] = @id
56
+ trans_unit_node['xml:space'] = @xml_space
57
+
58
+ trans_unit_node.add_leaf_node(element: 'source', content: @source)
59
+ trans_unit_node.add_leaf_node(element: 'target', content: @target)
60
+
61
+ return trans_unit_node if @note.nil?
62
+
63
+ trans_unit_node.add_leaf_node(element: 'note', content: @note)
64
+
65
+ trans_unit_node
66
+ end
67
+
68
+ # Encode this `Entry` object to an XML string
69
+ #
70
+ # @return [String]
71
+ def to_s
72
+ to_xml.to_s.strip
73
+ end
74
+
75
+ # Decode the given XML into an `Entry` object, if possible
76
+ #
77
+ # Raises for invalid input
78
+ #
79
+ # @return [Entry, nil]
80
+ def self.from_xml(xml)
81
+ validate_source_xml(xml)
82
+
83
+ Entry.new(
84
+ id: xml['id'],
85
+ source: xml.at('source').content,
86
+ target: xml.at('target').content,
87
+ note: xml.at('note').content || nil,
88
+ xml_space: xml['xml:space']
89
+ )
90
+ end
91
+
92
+ # Validate the given XML to ensure that it's a valid `<trans-unit>` element
93
+ #
94
+ # @return [void]
95
+ def self.validate_source_xml(xml)
96
+ raise 'Entry XML is nil' if xml.nil?
97
+ raise "Invalid Entry XML – must be a nokogiri object, got `#{xml.class}`" unless xml.is_a? Nokogiri::XML::Element
98
+ raise 'Invalid Entry XML – the root node must be `<trans-unit>`' if xml.name != 'trans-unit'
99
+ end
100
+ end
101
+ end
data/lib/xliff/file.rb ADDED
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xliff
4
+ # Models a single file for translation
5
+ class File
6
+ # The file's headers
7
+ # @return [Array<Header>]
8
+ attr_reader :headers
9
+
10
+ # The file's translation entries
11
+ # @return [Array<Header>]
12
+ attr_reader :entries
13
+
14
+ # The file's name in the original project (used for reference when translating)
15
+ # @return [String]
16
+ attr_reader :original
17
+
18
+ # The locale code for the source language
19
+ #
20
+ # @return [String]
21
+ attr_reader :source_language
22
+
23
+ # The locale code for the translated language
24
+ #
25
+ # This usually matches the `source_language` for files to be translated – it will differ if the file has
26
+ # been translated.
27
+ #
28
+ # @return [String]
29
+ attr_reader :target_language
30
+
31
+ # The type of data represented
32
+ #
33
+ # There are a variety of programming languages that can be represented by the XLIFF spec. Defaults to `plaintext`.
34
+ # @return [String]
35
+ attr_reader :datatype
36
+
37
+ # Create a blank File object
38
+ #
39
+ # Most often used to build an XLIFF file by hand.
40
+ #
41
+ # @param [String] original The original file name.
42
+ # @param [String] source_language The locale code for the source language.
43
+ # @param [String] target_language The locale code for the translated language.
44
+ # @param [String] datatype The type of data represented.
45
+ def initialize(original:, source_language:, target_language:, datatype: 'plaintext')
46
+ @original = original
47
+ @source_language = source_language
48
+ @target_language = target_language
49
+ @datatype = datatype
50
+
51
+ @headers = []
52
+ @entries = []
53
+ end
54
+
55
+ # Add arbitrary header data to the file
56
+ #
57
+ # @param [Xliff::Header] header A translation file header.
58
+ # @return [void]
59
+ def add_header(header)
60
+ raise unless header.is_a? Xliff::Header
61
+
62
+ @headers << header
63
+ end
64
+
65
+ # Add a translation entry to the file
66
+ #
67
+ # @param [Xliff::Entry] entry A translation unit.
68
+ # @return [void]
69
+ def add_entry(entry)
70
+ raise unless entry.is_a? Xliff::Entry
71
+
72
+ @entries << entry
73
+ end
74
+
75
+ # Find the first entry with a given `id`, if present
76
+ #
77
+ # @param [String] entry The `id` to search for.
78
+ # @return [Xliff::Entry, nil]
79
+ def entry_with_id(id)
80
+ @entries.find do |entry|
81
+ entry.id == id
82
+ end
83
+ end
84
+
85
+ # Encode this {File} object as an XLIFF document fragment representing the {File}
86
+ #
87
+ # Also encodes any headers and translation strings as children of the `File` element.
88
+ #
89
+ # @return [Nokogiri::XML.fragment]
90
+ def to_xml
91
+ fragment = Nokogiri::XML.fragment('')
92
+ file_node = fragment.document.create_element('file')
93
+ file_node['original'] = @original
94
+ file_node['source-language'] = @source_language
95
+ file_node['target-language'] = @target_language
96
+ file_node['datatype'] = @datatype
97
+
98
+ add_headers_to_file(fragment, file_node)
99
+ add_entries_to_file(fragment, file_node)
100
+
101
+ file_node
102
+ end
103
+
104
+ # Encode this {File} object to an XML string
105
+ #
106
+ # @return [String]
107
+ def to_s
108
+ to_xml.to_xml
109
+ end
110
+
111
+ # Decode the given XML into an {Xliff::File} object, if possible
112
+ #
113
+ # Raises for invalid input, and parses all child translation entries.
114
+ #
115
+ # @param [Nokogiri::XML::Element, #read] xml An XLIFF `<file>` fragment.
116
+ # @return [File]
117
+ def self.from_xml(xml)
118
+ validate_source_xml(xml)
119
+
120
+ file = File.new(
121
+ original: xml['original'],
122
+ source_language: xml['source-language'],
123
+ target_language: xml['target-language'],
124
+ datatype: xml['datatype'] || nil
125
+ )
126
+
127
+ import_file_header(xml, file)
128
+ import_file_body(xml, file)
129
+
130
+ file
131
+ end
132
+
133
+ # Run a series of validations against the input XML
134
+ #
135
+ # Automatically run prior to attempting to parse using `from_xml`.
136
+ #
137
+ # @raise [ExceptionClass] Raises exceptions if the input XML does not match expectations.
138
+ # @return [void]
139
+ def self.validate_source_xml(xml)
140
+ raise 'File XML is nil' if xml.nil?
141
+ raise "Invalid File XML – must be a nokogiri object, got `#{xml.class}`" unless xml.is_a? Nokogiri::XML::Element
142
+ raise 'Invalid File XML – the root node must be `<file>`' if xml.name != 'file'
143
+ end
144
+
145
+ # Import File Header Tags from given XML
146
+ #
147
+ # Parses the `<header>` XML tag and imports any headers into the file.
148
+ #
149
+ # @api private
150
+ # @param [Nokogiri::XML::Element, #read] xml An XLIFF `<file>` fragment.
151
+ # @param [File] file The {File} object being created.
152
+ # @return [void]
153
+ private_class_method def self.import_file_header(xml, file)
154
+ return if xml.at('header').nil?
155
+
156
+ xml.at('header').element_children.each { |node| file.add_header Header.from_xml(node) }
157
+ end
158
+
159
+ # Import File <trans-unit> Tags from given XML
160
+ #
161
+ # Parses the `<body>` XML tag and imports any translation entries into the file.
162
+ #
163
+ # @api private
164
+ # @param [Nokogiri::XML::Element, #read] xml An XLIFF `<file>` fragment.
165
+ # @param [File] file The {File} object being created.
166
+ # @return [void]
167
+ private_class_method def self.import_file_body(xml, file)
168
+ return if xml.at('body').nil?
169
+
170
+ xml.at('body').element_children.each { |node| file.add_entry Entry.from_xml(node) }
171
+ end
172
+
173
+ private
174
+
175
+ # Encode the file headers into their XML representation
176
+ #
177
+ # @api private
178
+ # @return [void]
179
+ def add_headers_to_file(fragment, node)
180
+ return if @headers.empty?
181
+
182
+ header = Nokogiri::XML::Node.new('header', fragment.document)
183
+ @headers.each do |h|
184
+ header.add_child(h.to_xml)
185
+ end
186
+ node.add_child(header)
187
+ end
188
+
189
+ # Encode the file's translation entries into their XML representation
190
+ #
191
+ # @api private
192
+ # @return [void]
193
+ def add_entries_to_file(fragment, node)
194
+ return if @entries.empty?
195
+
196
+ body = Nokogiri::XML::Node.new('body', fragment.document)
197
+ @entries.each do |entry|
198
+ body.add_child(entry.to_xml)
199
+ end
200
+ node.add_child(body)
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module Xliff
6
+ # Models a file header.
7
+ #
8
+ # Headers have an element and a set of key/value pairs encoded as XML attributes.
9
+ class Header
10
+ # This header's element
11
+ # @return [String]
12
+ attr_reader :element
13
+
14
+ # This header's element
15
+ # @return [Hash<String, String>]
16
+ attr_reader :attributes
17
+
18
+ # Create a blank Header object
19
+ #
20
+ # Most often used to build an XLIFF file by hand.
21
+ #
22
+ # @param [String] element The XML element to use.
23
+ # @param [String: String] attributes Any attributes that should be set on the header.
24
+ def initialize(element: nil, attributes: {})
25
+ @element = element
26
+ @attributes = attributes.transform_values(&:to_s)
27
+ end
28
+
29
+ # Encode this {Xliff::Header} object as an Nokogiri XML Element Representation of this header's expected element
30
+ #
31
+ # @return [Nokogiri::XML.fragment]
32
+ def to_xml
33
+ fragment = Nokogiri::XML.fragment('')
34
+ node = fragment.document.create_element(@element)
35
+
36
+ @attributes.each do |key, value|
37
+ node[key] = value
38
+ end
39
+
40
+ node
41
+ end
42
+
43
+ # Encode this {Header} object to an XML string
44
+ #
45
+ # @return [String]
46
+ def to_s
47
+ to_xml.to_xml
48
+ end
49
+
50
+ # Decode the given XML into an {Xliff::Header} object, if possible
51
+ #
52
+ # Raises for invalid input.
53
+ #
54
+ # @param [Nokogiri::XML::Element] xml An XLIFF header fragment.
55
+ # @return [Header]
56
+ def self.from_xml(xml)
57
+ raise 'Header XML is nil' if xml.nil?
58
+ raise "Invalid Header XML – must be a nokogiri object, got `#{xml.class}`" unless xml.is_a? Nokogiri::XML::Element
59
+
60
+ Header.new(
61
+ element: xml.name,
62
+ attributes: xml.keys.to_h { |k| [k, xml[k]] }
63
+ )
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extensions of the Nokogiri gem for use with this project.
4
+ module Nokogiri
5
+ # Customizations to the Nokogiri XML namespace.
6
+ module XML
7
+ # Helpers for operating on XML Elements
8
+ class Element
9
+ # Adds a simple Text Node as a child element
10
+ #
11
+ # @param [String] element The XML tag name to use.
12
+ # @param [String] content The text contents of the XML tag.
13
+ # @example Look up an existing file
14
+ # # To Generate <text>Hello World</text>:
15
+ # xml.add_leaf_node(element: 'text', content: 'Hello World')
16
+ # @api private
17
+ # @return [Void]
18
+ def add_leaf_node(element:, content:)
19
+ node = document.create_element(element)
20
+ node.content = content
21
+ add_child(node)
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/xliff.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'xliff/bundle'
4
+ require_relative 'xliff/entry'
5
+ require_relative 'xliff/file'
6
+ require_relative 'xliff/header'
7
+ require_relative 'xliff/xml_extensions'
8
+
9
+ # Namespace for classes and modules that handle building and parsing XLIFF files.
10
+ # @api public
11
+ module Xliff; end
data/sig/xliff.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Xliff
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xliff
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Automattic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ description: Read and write xliff files
28
+ email:
29
+ - mobile@automattic.com
30
+ executables:
31
+ - console
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".buildkite/pipeline.yml"
36
+ - ".bundle/config"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - ".ruby-version"
40
+ - CHANGELOG.md
41
+ - CODE_OF_CONDUCT.md
42
+ - Dangerfile
43
+ - Gemfile
44
+ - Gemfile.lock
45
+ - LICENSE.txt
46
+ - Makefile
47
+ - README.md
48
+ - Rakefile
49
+ - bin/console
50
+ - lib/xliff.rb
51
+ - lib/xliff/bundle.rb
52
+ - lib/xliff/entry.rb
53
+ - lib/xliff/file.rb
54
+ - lib/xliff/header.rb
55
+ - lib/xliff/xml_extensions.rb
56
+ - sig/xliff.rbs
57
+ homepage: https://github.com/automattic/xliff
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ homepage_uri: https://github.com/automattic/xliff
62
+ source_code_uri: https://github.com/automattic/xliff
63
+ changelog_uri: https://github.com/automattic/xliff/CHANGELOG.md
64
+ rubygems_mfa_required: 'false'
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.7.4
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.1.6
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Manage xliff files from Ruby
84
+ test_files: []