twine 1.0.6 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f6d46a283d86aa50c84b7b229f2e3159edd72816
4
- data.tar.gz: c412942ce007dbd236cc32b4b9ceadf99734cbe4
2
+ SHA256:
3
+ metadata.gz: d8667f50ad7147b8b7edac0d82ce542c2ecc973604c8334e878513b6acaa9a14
4
+ data.tar.gz: 1ceb2b2b18dea8a3585bd91a1b427b30ebd9c888817dcf7140b3d211cd9e157c
5
5
  SHA512:
6
- metadata.gz: b44a73df4eb789f78cbec2a37cbe496f7cda95ea62b7e56c2eb189d5f26b5c0a5ef497b7c185a78961ba263df64e386a7efc2b58a58dab540eb0ea9a81b68922
7
- data.tar.gz: c9814e961ac0c8eb36c61122c89b3d9bd321133dd8b0441e2cfdd352a65e71d74cf8cae31d0da9903988e80c524fef332dd5fc56c2d4dc622e3f581364770636
6
+ metadata.gz: a961953ec7b74510c10e17114b84c227bac43117a869ba4254d7410a16ea9413e4133a2c9f099c3d67922fd6e1632d841f366e79d28687e61f73861721dea43a
7
+ data.tar.gz: 954b69b0d6e0fadec38cf145e0e1772eb1fde0259337b45ffac172d41b6f6c031b33373c768cfbb48ad3e9cfb1565dc9e91db8e825023421fdc30098439a4d86
@@ -45,6 +45,14 @@ module Twine
45
45
  files are UTF-16 without BOM, you need to specify if it's UTF-16LE or UTF16-BE.
46
46
  DESC
47
47
  },
48
+ escape_all_tags: {
49
+ switch: ['--[no-]escape-all-tags'],
50
+ description: <<-DESC,
51
+ Always escape all HTML tags. By default the Android formatter will ONLY escape styling tags, if a
52
+ string also contains placeholders. This flag enforces that styling tags are escaped regardless of
53
+ placeholders.
54
+ DESC
55
+ },
48
56
  file_name: {
49
57
  switch: ['-n', '--file-name FILE_NAME'],
50
58
  description: 'This flag may be used to overwrite the default file name of the format.'
@@ -111,6 +119,7 @@ module Twine
111
119
  optional_options: [
112
120
  :developer_language,
113
121
  :encoding,
122
+ :escape_all_tags,
114
123
  :format,
115
124
  :include,
116
125
  :languages,
@@ -133,6 +142,7 @@ module Twine
133
142
  :create_folders,
134
143
  :developer_language,
135
144
  :encoding,
145
+ :escape_all_tags,
136
146
  :file_name,
137
147
  :format,
138
148
  :include,
@@ -152,6 +162,7 @@ module Twine
152
162
  optional_options: [
153
163
  :developer_language,
154
164
  :encoding,
165
+ :escape_all_tags,
155
166
  :include,
156
167
  :quiet,
157
168
  :tags,
@@ -125,7 +125,7 @@ module Twine
125
125
  # if not, escape opening angle brackes unless it's a supported styling tag
126
126
  # https://github.com/scelis/twine/issues/212
127
127
  # https://stackoverflow.com/questions/3235131/#18199543
128
- if number_of_twine_placeholders(value) > 0
128
+ if number_of_twine_placeholders(value) > 0 or @options[:escape_all_tags]
129
129
  # matches all `<` but <![CDATA
130
130
  angle_bracket = /<(?!(\/?(\!\[CDATA)))/
131
131
  else
@@ -1,5 +1,6 @@
1
1
  module Twine
2
2
  module Formatters
3
+ # For a description of the .po file format, see https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
3
4
  class Django < Abstract
4
5
  def format_name
5
6
  'django'
@@ -14,9 +15,9 @@ module Twine
14
15
  end
15
16
 
16
17
  def read(io, lang)
17
- comment_regex = /#\. *"?(.*)"?$/
18
- key_regex = /msgid *"(.*)"$/
19
- value_regex = /msgstr *"(.*)"$/m
18
+ comment_regex = /^\s*#\. *"?(.*)"?$/
19
+ key_regex = /^msgid *"(.*)"$/
20
+ value_regex = /^msgstr *"(.*)"$/m
20
21
 
21
22
  while line = io.gets
22
23
  comment_match = comment_regex.match(line)
@@ -53,11 +54,12 @@ module Twine
53
54
  end
54
55
 
55
56
  def format_header(lang)
56
- "##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\""
57
+ # see https://www.gnu.org/software/trans-coord/manual/gnun/html_node/PO-Header.html for details
58
+ "# Django Strings File\n# Generated by Twine #{Twine::VERSION}\n# Language: #{lang}\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\""
57
59
  end
58
60
 
59
61
  def format_section_header(section)
60
- "#--------- #{section.name} ---------#\n"
62
+ "# --------- #{section.name} --------- #\n"
61
63
  end
62
64
 
63
65
  def format_definition(definition, lang)
@@ -72,5 +72,11 @@ module Twine
72
72
  def convert_placeholders_from_flash_to_twine(input)
73
73
  input.gsub /\{\d+\}/, '%@'
74
74
  end
75
+
76
+ # Python supports placeholders in the form of `%(amount)03d`
77
+ # see https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting
78
+ def contains_python_specific_placeholder(input)
79
+ /%\([a-zA-Z0-9_-]+\)#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH}#{PLACEHOLDER_TYPES}/.match(input) != nil
80
+ end
75
81
  end
76
82
  end
@@ -241,6 +241,7 @@ module Twine
241
241
  duplicate_keys = Set.new
242
242
  keys_without_tags = Set.new
243
243
  invalid_keys = Set.new
244
+ keys_with_python_only_placeholders = Set.new
244
245
  valid_key_regex = /^[A-Za-z0-9_]+$/
245
246
 
246
247
  @twine_file.sections.each do |section|
@@ -253,6 +254,8 @@ module Twine
253
254
  keys_without_tags.add(definition.key) if definition.tags == nil or definition.tags.length == 0
254
255
 
255
256
  invalid_keys << definition.key unless definition.key =~ valid_key_regex
257
+
258
+ keys_with_python_only_placeholders << definition.key if definition.translations.values.any? { |v| Placeholders.contains_python_specific_placeholder(v) }
256
259
  end
257
260
  end
258
261
 
@@ -275,6 +278,10 @@ module Twine
275
278
  errors << "Found key(s) with invalid characters:\n#{join_keys.call(invalid_keys)}"
276
279
  end
277
280
 
281
+ unless keys_with_python_only_placeholders.empty?
282
+ errors << "Found key(s) with placeholders that are only supported by Python:\n#{join_keys.call(keys_with_python_only_placeholders)}"
283
+ end
284
+
278
285
  raise Twine::Error.new errors.join("\n\n") unless errors.empty?
279
286
 
280
287
  Twine::stdout.puts "#{@options[:twine_file]} is valid."
@@ -1,3 +1,3 @@
1
1
  module Twine
2
- VERSION = '1.0.6'
2
+ VERSION = '1.1'
3
3
  end
@@ -1,12 +1,11 @@
1
- ##
2
- # Django Strings File
3
- # Generated by Twine <%= Twine::VERSION %>
4
- # Language: en
1
+ # Django Strings File
2
+ # Generated by Twine <%= Twine::VERSION %>
3
+ # Language: en
5
4
  msgid ""
6
5
  msgstr ""
7
6
  "Content-Type: text/plain; charset=UTF-8\n"
8
7
 
9
- #--------- Section 1 ---------#
8
+ # --------- Section 1 --------- #
10
9
 
11
10
  #. comment key1
12
11
  # base translation: "value1-english"
@@ -18,7 +17,7 @@ msgid "key2"
18
17
  msgstr "value2-english"
19
18
 
20
19
 
21
- #--------- Section 2 ---------#
20
+ # --------- Section 2 --------- #
22
21
 
23
22
  # base translation: "value3-english"
24
23
  msgid "key3"
@@ -48,6 +48,13 @@ class CLITest < TwineTest
48
48
  assert_equal 'UTF16', @options[:encoding]
49
49
  end
50
50
 
51
+ def assert_option_escape_all_tags
52
+ parse_with "--escape-all-tags"
53
+ assert @options[:escape_all_tags]
54
+ parse_with "--no-escape-all-tags"
55
+ refute @options[:escape_all_tags]
56
+ end
57
+
51
58
  def assert_option_format
52
59
  random_format = Twine::Formatters.formatters.sample.format_name.downcase
53
60
  parse_with "--format #{random_format}"
@@ -177,6 +184,7 @@ class TestGenerateLocalizationFileCLI < CLITest
177
184
  assert_help
178
185
  assert_option_developer_language
179
186
  assert_option_encoding
187
+ assert_option_escape_all_tags
180
188
  assert_option_format
181
189
  assert_option_include
182
190
  assert_option_single_language
@@ -217,6 +225,7 @@ class TestGenerateAllLocalizationFilesCLI < CLITest
217
225
  assert_help
218
226
  assert_option_developer_language
219
227
  assert_option_encoding
228
+ assert_option_escape_all_tags
220
229
  assert_option_format
221
230
  assert_option_include
222
231
  assert_option_quiet
@@ -268,6 +277,7 @@ class TestGenerateLocalizationArchiveCLI < CLITest
268
277
  assert_help
269
278
  assert_option_developer_language
270
279
  assert_option_encoding
280
+ assert_option_escape_all_tags
271
281
  assert_option_include
272
282
  assert_option_quiet
273
283
  assert_option_tags
@@ -138,6 +138,11 @@ class TestAndroidFormatter < FormatterTest
138
138
  '<xliff:g id="42">untouched</xliff:g>' => '<xliff:g id="42">untouched</xliff:g>',
139
139
  '<xliff:g id="1">first</xliff:g> inbetween <xliff:g id="2">second</xliff:g>' => '<xliff:g id="1">first</xliff:g> inbetween <xliff:g id="2">second</xliff:g>'
140
140
  }
141
+ @escape_all_test_values = {
142
+ '<b>bold</b>' => '&lt;b>bold&lt;/b>',
143
+ '<i>italic</i>' => '&lt;i>italic&lt;/i>',
144
+ '<u>underline</u>' => '&lt;u>underline&lt;/u>'
145
+ }
141
146
  end
142
147
 
143
148
  def test_read_format
@@ -217,6 +222,11 @@ class TestAndroidFormatter < FormatterTest
217
222
  @formatter.set_translation_for_key 'key1', 'en', input
218
223
  assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
219
224
  end
225
+
226
+ @escape_all_test_values.each do |expected, input|
227
+ @formatter.set_translation_for_key 'key1', 'en', input
228
+ assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en']
229
+ end
220
230
  end
221
231
 
222
232
  def test_format_file
@@ -245,6 +255,11 @@ class TestAndroidFormatter < FormatterTest
245
255
  @escape_test_values.each do |input, expected|
246
256
  assert_equal expected, @formatter.format_value(input)
247
257
  end
258
+
259
+ @formatter.options.merge!({ escape_all_tags: true })
260
+ @escape_all_test_values.each do |input, expected|
261
+ assert_equal expected, @formatter.format_value(input)
262
+ end
248
263
  end
249
264
 
250
265
  def test_format_value_escapes_non_resource_identifier_at_signs
@@ -539,6 +554,19 @@ class TestDjangoFormatter < FormatterTest
539
554
  language = %w(en-GB de fr).sample
540
555
  assert_equal language, @formatter.determine_language_given_path("/output/#{language}/#{@formatter.default_file_name}")
541
556
  end
557
+
558
+ def test_ignores_commented_out_strings
559
+ content = <<-EOCONTENT
560
+ #~ msgid "foo"
561
+ #~ msgstr "This should be ignored"
562
+ EOCONTENT
563
+
564
+ io = StringIO.new(content)
565
+
566
+ @formatter.read io, 'en'
567
+
568
+ assert_nil @empty_twine_file.definitions_by_key["foo"]
569
+ end
542
570
  end
543
571
 
544
572
  class TestFlashFormatter < FormatterTest
@@ -122,4 +122,21 @@ class PlaceholderTest < TwineTest
122
122
  assert_equal "some %@ more %@ text %@", from_flash("some {0} more {1} text {2}")
123
123
  end
124
124
  end
125
+
126
+ class PythonPlaceholder < PlaceholderTest
127
+ def test_negative_for_regular_placeholders
128
+ assert_equal false, Twine::Placeholders.contains_python_specific_placeholder(placeholder)
129
+ end
130
+
131
+ def test_positive_for_named_placeholders
132
+ inputs = [
133
+ "%(language)s has",
134
+ "For %(number)03d quotes",
135
+ "bought on %(app_name)s"
136
+ ]
137
+ inputs.each do |input|
138
+ assert_equal true, Twine::Placeholders.contains_python_specific_placeholder(input)
139
+ end
140
+ end
141
+ end
125
142
  end
@@ -58,4 +58,12 @@ class TestValidateTwineFile < CommandTest
58
58
  Twine::Runner.new(@options.merge(pedantic: true), @twine_file).validate_twine_file
59
59
  end
60
60
  end
61
+
62
+ def test_reports_python_specific_placeholders
63
+ random_definition.translations["en"] = "%(python_only)s"
64
+
65
+ assert_raises Twine::Error do
66
+ Twine::Runner.new(@options, @twine_file).validate_twine_file
67
+ end
68
+ end
61
69
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: '1.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Celis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-28 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.1'
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.1'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: safe_yaml
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.4'
47
+ version: '13.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.4'
54
+ version: '13.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -158,7 +158,7 @@ files:
158
158
  - test/test_validate_twine_file.rb
159
159
  - test/twine_file_dsl.rb
160
160
  - test/twine_test.rb
161
- homepage: https://github.com/mobiata/twine
161
+ homepage: https://github.com/scelis/twine
162
162
  licenses:
163
163
  - BSD-3-Clause
164
164
  metadata: {}
@@ -170,15 +170,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
172
172
  - !ruby/object:Gem::Version
173
- version: '2.0'
173
+ version: '2.4'
174
174
  required_rubygems_version: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - ">="
177
177
  - !ruby/object:Gem::Version
178
178
  version: '0'
179
179
  requirements: []
180
- rubyforge_project:
181
- rubygems_version: 2.5.2.3
180
+ rubygems_version: 3.1.2
182
181
  signing_key:
183
182
  specification_version: 4
184
183
  summary: Manage strings and their translations for your iOS, Android and other projects.