twine 0.7.0 → 0.8.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -0
  3. data/lib/twine.rb +21 -0
  4. data/lib/twine/cli.rb +68 -91
  5. data/lib/twine/formatters/abstract.rb +133 -82
  6. data/lib/twine/formatters/android.rb +49 -67
  7. data/lib/twine/formatters/apple.rb +33 -47
  8. data/lib/twine/formatters/django.rb +38 -48
  9. data/lib/twine/formatters/flash.rb +25 -40
  10. data/lib/twine/formatters/gettext.rb +37 -44
  11. data/lib/twine/formatters/jquery.rb +31 -33
  12. data/lib/twine/formatters/tizen.rb +38 -55
  13. data/lib/twine/output_processor.rb +57 -0
  14. data/lib/twine/placeholders.rb +54 -0
  15. data/lib/twine/runner.rb +78 -115
  16. data/lib/twine/stringsfile.rb +83 -60
  17. data/lib/twine/version.rb +1 -1
  18. data/test/command_test_case.rb +12 -0
  19. data/test/fixtures/consume_loc_drop.zip +0 -0
  20. data/test/fixtures/formatter_android.xml +15 -0
  21. data/test/fixtures/formatter_apple.strings +20 -0
  22. data/test/fixtures/formatter_django.po +28 -0
  23. data/test/fixtures/formatter_flash.properties +15 -0
  24. data/test/fixtures/formatter_gettext.po +26 -0
  25. data/test/fixtures/formatter_jquery.json +7 -0
  26. data/test/fixtures/formatter_tizen.xml +15 -0
  27. data/test/fixtures/gettext_multiline.po +10 -0
  28. data/test/fixtures/twine_accent_values.txt +13 -0
  29. data/test/test_abstract_formatter.rb +152 -0
  30. data/test/test_cli.rb +288 -0
  31. data/test/test_consume_loc_drop.rb +27 -0
  32. data/test/test_consume_string_file.rb +53 -0
  33. data/test/test_formatters.rb +236 -0
  34. data/test/test_generate_all_string_files.rb +44 -0
  35. data/test/test_generate_loc_drop.rb +44 -0
  36. data/test/test_generate_string_file.rb +51 -0
  37. data/test/test_output_processor.rb +85 -0
  38. data/test/test_placeholders.rb +86 -0
  39. data/test/test_strings_file.rb +58 -0
  40. data/test/test_strings_row.rb +47 -0
  41. data/test/test_validate_strings_file.rb +55 -0
  42. data/test/twine_file_dsl.rb +46 -0
  43. data/test/twine_test_case.rb +44 -0
  44. metadata +80 -37
  45. data/test/fixtures/en-1.json +0 -5
  46. data/test/fixtures/en-1.po +0 -16
  47. data/test/fixtures/en-1.strings +0 -10
  48. data/test/fixtures/en-2.po +0 -23
  49. data/test/fixtures/en-3.xml +0 -8
  50. data/test/fixtures/fr-1.xml +0 -10
  51. data/test/fixtures/strings-1.txt +0 -17
  52. data/test/fixtures/strings-2.txt +0 -5
  53. data/test/fixtures/strings-3.txt +0 -5
  54. data/test/fixtures/test-json-line-breaks/consumed.txt +0 -5
  55. data/test/fixtures/test-json-line-breaks/generated.json +0 -3
  56. data/test/fixtures/test-json-line-breaks/line-breaks.json +0 -3
  57. data/test/fixtures/test-json-line-breaks/line-breaks.txt +0 -4
  58. data/test/fixtures/test-output-1.txt +0 -12
  59. data/test/fixtures/test-output-10.txt +0 -9
  60. data/test/fixtures/test-output-11.txt +0 -9
  61. data/test/fixtures/test-output-12.txt +0 -12
  62. data/test/fixtures/test-output-2.txt +0 -12
  63. data/test/fixtures/test-output-3.txt +0 -18
  64. data/test/fixtures/test-output-4.txt +0 -21
  65. data/test/fixtures/test-output-5.txt +0 -4
  66. data/test/fixtures/test-output-6.txt +0 -10
  67. data/test/fixtures/test-output-7.txt +0 -16
  68. data/test/fixtures/test-output-8.txt +0 -9
  69. data/test/fixtures/test-output-9.txt +0 -21
  70. data/test/twine_test.rb +0 -134
@@ -14,6 +14,8 @@ module Twine
14
14
  attr_accessor :comment
15
15
  attr_accessor :tags
16
16
  attr_reader :translations
17
+ attr_accessor :reference
18
+ attr_accessor :reference_key
17
19
 
18
20
  def initialize(key)
19
21
  @key = key
@@ -22,37 +24,36 @@ module Twine
22
24
  @translations = {}
23
25
  end
24
26
 
27
+ def comment
28
+ raw_comment || (reference.comment if reference)
29
+ end
30
+
31
+ def raw_comment
32
+ @comment
33
+ end
34
+
25
35
  def matches_tags?(tags, include_untagged)
26
- if tags == nil || tags.length == 0
36
+ if tags == nil || tags.empty?
27
37
  # The user did not specify any tags. Everything passes.
28
38
  return true
29
- elsif @tags == nil || @tags.length == 0
39
+ elsif @tags == nil
30
40
  # This row has no tags.
31
- return (include_untagged) ? true : false
41
+ return reference ? reference.matches_tags?(tags, include_untagged) : include_untagged
42
+ elsif @tags.empty?
43
+ return include_untagged
32
44
  else
33
- tags.each do |tag|
34
- if @tags.include? tag
35
- return true
36
- end
37
- end
45
+ return !(tags & @tags).empty?
38
46
  end
39
47
 
40
48
  return false
41
49
  end
42
50
 
43
- def translated_string_for_lang(lang, default_lang=nil)
44
- if @translations[lang]
45
- return @translations[lang]
46
- elsif default_lang.respond_to?("each")
47
- default_lang.each do |def_lang|
48
- if @translations[def_lang]
49
- return @translations[def_lang]
50
- end
51
- end
52
- return nil
53
- else
54
- return @translations[default_lang]
55
- end
51
+ def translated_string_for_lang(lang)
52
+ translation = [lang].flatten.map { |l| @translations[l] }.first
53
+
54
+ translation = reference.translated_string_for_lang(lang) if translation.nil? && reference
55
+
56
+ return translation
56
57
  end
57
58
  end
58
59
 
@@ -61,12 +62,38 @@ module Twine
61
62
  attr_reader :strings_map
62
63
  attr_reader :language_codes
63
64
 
65
+ private
66
+
67
+ def match_key(text)
68
+ match = /^\[(.+)\]$/.match(text)
69
+ return match[1] if match
70
+ end
71
+
72
+ public
73
+
64
74
  def initialize
65
75
  @sections = []
66
76
  @strings_map = {}
67
77
  @language_codes = []
68
78
  end
69
79
 
80
+ def add_language_code(code)
81
+ if @language_codes.length == 0
82
+ @language_codes << code
83
+ elsif !@language_codes.include?(code)
84
+ dev_lang = @language_codes[0]
85
+ @language_codes << code
86
+ @language_codes.delete(dev_lang)
87
+ @language_codes.sort!
88
+ @language_codes.insert(0, dev_lang)
89
+ end
90
+ end
91
+
92
+ def set_developer_language_code(code)
93
+ @language_codes.delete(code)
94
+ @language_codes.insert(0, code)
95
+ end
96
+
70
97
  def read(path)
71
98
  if !File.file?(path)
72
99
  raise Twine::Error.new("File does not exist: #{path}")
@@ -93,9 +120,9 @@ module Twine
93
120
  parsed = true
94
121
  end
95
122
  elsif line.length > 2 && line[0, 1] == '['
96
- match = /^\[(.+)\]$/.match(line)
97
- if match
98
- current_row = StringsRow.new(match[1])
123
+ key = match_key(line)
124
+ if key
125
+ current_row = StringsRow.new(key)
99
126
  @strings_map[current_row.key] = current_row
100
127
  if !current_section
101
128
  current_section = StringsSection.new('')
@@ -109,15 +136,16 @@ module Twine
109
136
  if match
110
137
  key = match[1].strip
111
138
  value = match[2].strip
112
- if value[0,1] == '`' && value[-1,1] == '`'
113
- value = value[1..-2]
114
- end
139
+
140
+ value = value[1..-2] if value[0] == '`' && value[-1] == '`'
115
141
 
116
142
  case key
117
- when "comment"
143
+ when 'comment'
118
144
  current_row.comment = value
119
145
  when 'tags'
120
146
  current_row.tags = value.split(',')
147
+ when 'ref'
148
+ current_row.reference_key = value if value
121
149
  else
122
150
  if !@language_codes.include? key
123
151
  add_language_code(key)
@@ -133,6 +161,12 @@ module Twine
133
161
  end
134
162
  end
135
163
  end
164
+
165
+ # resolve_references
166
+ @strings_map.each do |key, row|
167
+ next unless row.reference_key
168
+ row.reference = @strings_map[row.reference_key]
169
+ end
136
170
  end
137
171
 
138
172
  def write(path)
@@ -148,54 +182,43 @@ module Twine
148
182
 
149
183
  section.rows.each do |row|
150
184
  f.puts "\t[#{row.key}]"
151
- value = row.translations[dev_lang]
152
- if !value
185
+
186
+ value = write_value(row, dev_lang, f)
187
+ if !value && !row.reference_key
153
188
  puts "Warning: #{row.key} does not exist in developer language '#{dev_lang}'"
154
- else
155
- if value[0,1] == ' ' || value[-1,1] == ' ' || (value[0,1] == '`' && value[-1,1] == '`')
156
- value = '`' + value + '`'
157
- end
158
- f.puts "\t\t#{dev_lang} = #{value}"
159
189
  end
160
-
190
+
191
+ if row.reference_key
192
+ f.puts "\t\tref = #{row.reference_key}"
193
+ end
161
194
  if row.tags && row.tags.length > 0
162
195
  tag_str = row.tags.join(',')
163
196
  f.puts "\t\ttags = #{tag_str}"
164
197
  end
165
- if row.comment && row.comment.length > 0
166
- f.puts "\t\tcomment = #{row.comment}"
198
+ if row.raw_comment and row.raw_comment.length > 0
199
+ f.puts "\t\tcomment = #{row.raw_comment}"
167
200
  end
168
201
  @language_codes[1..-1].each do |lang|
169
- value = row.translations[lang]
170
- if value
171
- if value[0,1] == ' ' || value[-1,1] == ' ' || (value[0,1] == '`' && value[-1,1] == '`')
172
- value = '`' + value + '`'
173
- end
174
- f.puts "\t\t#{lang} = #{value}"
175
- end
202
+ write_value(row, lang, f)
176
203
  end
177
204
  end
178
205
  end
179
206
  end
180
207
  end
181
208
 
182
- def add_language_code(code)
183
- if @language_codes.length == 0
184
- @language_codes << code
185
- elsif !@language_codes.include?(code)
186
- dev_lang = @language_codes[0]
187
- @language_codes << code
188
- @language_codes.delete(dev_lang)
189
- @language_codes.sort!
190
- @language_codes.insert(0, dev_lang)
191
- end
192
- end
209
+ private
193
210
 
194
- def set_developer_language_code(code)
195
- if @language_codes.include?(code)
196
- @language_codes.delete(code)
211
+ def write_value(row, language, file)
212
+ value = row.translations[language]
213
+ return nil unless value
214
+
215
+ if value[0] == ' ' || value[-1] == ' ' || (value[0] == '`' && value[-1] == '`')
216
+ value = '`' + value + '`'
197
217
  end
198
- @language_codes.insert(0, code)
218
+
219
+ file.puts "\t\t#{language} = #{value}"
220
+ return value
199
221
  end
222
+
200
223
  end
201
224
  end
@@ -1,3 +1,3 @@
1
1
  module Twine
2
- VERSION = '0.7.0'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -0,0 +1,12 @@
1
+ require 'twine_test_case'
2
+
3
+ class CommandTestCase < TwineTestCase
4
+ def prepare_mock_formatter(formatter_class)
5
+ strings = Twine::StringsFile.new
6
+ strings.language_codes.concat KNOWN_LANGUAGES
7
+
8
+ formatter = formatter_class.new(strings, {})
9
+ formatter_class.stubs(:new).returns(formatter)
10
+ formatter
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Android Strings File -->
3
+ <!-- Generated by Twine <%= Twine::VERSION %> -->
4
+ <!-- Language: en -->
5
+ <resources>
6
+ <!-- SECTION: Section 1 -->
7
+ <!-- comment key1 -->
8
+ <string name="key1">value1-english</string>
9
+ <string name="key2">value2-english</string>
10
+
11
+ <!-- SECTION: Section 2 -->
12
+ <string name="key3">value3-english</string>
13
+ <!-- comment key4 -->
14
+ <string name="key4">value4-english</string>
15
+ </resources>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Apple Strings File
3
+ * Generated by Twine <%= Twine::VERSION %>
4
+ * Language: en
5
+ */
6
+
7
+ /********** Section 1 **********/
8
+
9
+ /* comment key1 */
10
+ "key1" = "value1-english";
11
+
12
+ "key2" = "value2-english";
13
+
14
+
15
+ /********** Section 2 **********/
16
+
17
+ "key3" = "value3-english";
18
+
19
+ /* comment key4 */
20
+ "key4" = "value4-english";
@@ -0,0 +1,28 @@
1
+ ##
2
+ # Django Strings File
3
+ # Generated by Twine 0.7.0
4
+ # Language: en
5
+
6
+
7
+ #--------- Section 1 ---------#
8
+
9
+ #. comment key1
10
+ # base translation: "value1-english"
11
+ msgid "key1"
12
+ msgstr "value1-english"
13
+
14
+ # base translation: "value2-english"
15
+ msgid "key2"
16
+ msgstr "value2-english"
17
+
18
+
19
+ #--------- Section 2 ---------#
20
+
21
+ # base translation: "value3-english"
22
+ msgid "key3"
23
+ msgstr "value3-english"
24
+
25
+ #. comment key4
26
+ # base translation: "value4-english"
27
+ msgid "key4"
28
+ msgstr "value4-english"
@@ -0,0 +1,15 @@
1
+ ## Flash Strings File
2
+ ## Generated by Twine 0.7.0
3
+ ## Language: en
4
+
5
+ ## Section 1 ##
6
+
7
+ # comment key1
8
+ key1=value1-english
9
+ key2=value2-english
10
+
11
+ ## Section 2 ##
12
+
13
+ key3=value3-english
14
+ # comment key4
15
+ key4=value4-english
@@ -0,0 +1,26 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: en\n"
4
+ "X-Generator: Twine <%= Twine::VERSION %>\n"
5
+
6
+
7
+ # SECTION: Section 1
8
+ #. "comment key1"
9
+ msgctxt "key1"
10
+ msgid "value1-english"
11
+ msgstr "value1-english"
12
+
13
+ msgctxt "key2"
14
+ msgid "value2-english"
15
+ msgstr "value2-english"
16
+
17
+
18
+ # SECTION: Section 2
19
+ msgctxt "key3"
20
+ msgid "value3-english"
21
+ msgstr "value3-english"
22
+
23
+ #. "comment key4"
24
+ msgctxt "key4"
25
+ msgid "value4-english"
26
+ msgstr "value4-english"
@@ -0,0 +1,7 @@
1
+ {
2
+ "key1":"value1-english",
3
+ "key2":"value2-english",
4
+
5
+ "key3":"value3-english",
6
+ "key4":"value4-english"
7
+ }
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Tizen Strings File -->
3
+ <!-- Generated by Twine <%= Twine::VERSION %> -->
4
+ <!-- Language: en -->
5
+ <string_table Bversion="2.0.0.201311071819" Dversion="20120315">
6
+ <!-- SECTION: Section 1 -->
7
+ <!-- comment key1 -->
8
+ <text id="IDS_KEY1">value1-english</text>
9
+ <text id="IDS_KEY2">value2-english</text>
10
+
11
+ <!-- SECTION: Section 2 -->
12
+ <text id="IDS_KEY3">value3-english</text>
13
+ <!-- comment key4 -->
14
+ <text id="IDS_KEY4">value4-english</text>
15
+ </string_table>
@@ -0,0 +1,10 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: en\n"
4
+ "X-Generator: Twine\n"
5
+
6
+ msgctxt "key1"
7
+ msgid "key1"
8
+ msgstr "multi"
9
+ "line\n"
10
+ "string"
@@ -0,0 +1,13 @@
1
+ [[Section]]
2
+ [value_with_leading_accent]
3
+ en = `value
4
+ [value_with_trailing_accent]
5
+ en = value`
6
+ [value_with_leading_space]
7
+ en = ` value`
8
+ [value_with_trailing_space]
9
+ en = `value `
10
+ [value_wrapped_by_spaces]
11
+ en = ` value `
12
+ [value_wrapped_by_accents]
13
+ en = ``value``
@@ -0,0 +1,152 @@
1
+ require 'twine_test_case'
2
+
3
+ class TestAbstractFormatter < TwineTestCase
4
+ class SetTranslation < TwineTestCase
5
+ def setup
6
+ super
7
+
8
+ @strings = build_twine_file 'en', 'fr' do
9
+ add_section 'Section' do
10
+ add_row key1: 'value1-english'
11
+ add_row key2: { en: 'value2-english', fr: 'value2-french' }
12
+ end
13
+ end
14
+
15
+ @formatter = Twine::Formatters::Abstract.new(@strings, {})
16
+ end
17
+
18
+ def test_set_translation_updates_existing_value
19
+ @formatter.set_translation_for_key 'key1', 'en', 'value1-english-updated'
20
+
21
+ assert_equal 'value1-english-updated', @strings.strings_map['key1'].translations['en']
22
+ end
23
+
24
+ def test_set_translation_does_not_alter_other_language
25
+ @formatter.set_translation_for_key 'key2', 'en', 'value2-english-updated'
26
+
27
+ assert_equal 'value2-french', @strings.strings_map['key2'].translations['fr']
28
+ end
29
+
30
+ def test_set_translation_escapes_newlines
31
+ @formatter.set_translation_for_key 'key1', 'en', "new\nline"
32
+
33
+ assert_equal 'new\nline', @strings.strings_map['key1'].translations['en']
34
+ end
35
+
36
+ def test_set_translation_adds_translation_to_existing_key
37
+ @formatter.set_translation_for_key 'key1', 'fr', 'value1-french'
38
+
39
+ assert_equal 'value1-french', @strings.strings_map['key1'].translations['fr']
40
+ end
41
+
42
+ def test_set_translation_does_not_add_new_key
43
+ @formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
44
+
45
+ assert_nil @strings.strings_map['new-key']
46
+ end
47
+
48
+ def test_set_translation_consume_all_adds_new_key
49
+ formatter = Twine::Formatters::Abstract.new(@strings, { consume_all: true })
50
+ formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
51
+
52
+ assert_equal 'new-key-english', @strings.strings_map['new-key'].translations['en']
53
+ end
54
+
55
+ def test_set_translation_consume_all_adds_tags
56
+ random_tag = SecureRandom.uuid
57
+ formatter = Twine::Formatters::Abstract.new(@strings, { consume_all: true, tags: [random_tag] })
58
+ formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
59
+
60
+ assert_equal [random_tag], @strings.strings_map['new-key'].tags
61
+ end
62
+
63
+ def test_set_translation_adds_new_keys_to_category_uncategoriezed
64
+ formatter = Twine::Formatters::Abstract.new(@strings, { consume_all: true })
65
+ formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
66
+
67
+ assert_equal 'Uncategorized', @strings.sections[0].name
68
+ assert_equal 'new-key', @strings.sections[0].rows[0].key
69
+ end
70
+ end
71
+
72
+ class ValueReference < TwineTestCase
73
+ def setup
74
+ super
75
+
76
+ @strings = build_twine_file 'en', 'fr' do
77
+ add_section 'Section' do
78
+ add_row refkey: 'ref-value'
79
+ add_row key: :refkey
80
+ end
81
+ end
82
+
83
+ @formatter = Twine::Formatters::Abstract.new(@strings, {})
84
+ end
85
+
86
+ def test_set_translation_does_not_add_unchanged_translation
87
+ @formatter.set_translation_for_key 'key', 'en', 'ref-value'
88
+
89
+ assert_nil @strings.strings_map['key'].translations['en']
90
+ end
91
+
92
+ def test_set_translation_adds_changed_translation
93
+ @formatter.set_translation_for_key 'key', 'en', 'changed value'
94
+
95
+ assert_equal 'changed value', @strings.strings_map['key'].translations['en']
96
+ end
97
+ end
98
+
99
+ class SetComment < TwineTestCase
100
+ def setup
101
+ super
102
+
103
+ @strings = build_twine_file 'en' do
104
+ add_section 'Section' do
105
+ add_row key: 'value'
106
+ end
107
+ end
108
+ end
109
+
110
+ def test_set_comment_for_key_does_not_update_comment
111
+ formatter = Twine::Formatters::Abstract.new(@strings, {})
112
+ formatter.set_comment_for_key('key', 'comment')
113
+
114
+ assert_nil formatter.strings.strings_map['key'].comment
115
+ end
116
+
117
+ def test_set_comment_for_key_updates_comment_with_update_comments
118
+ formatter = Twine::Formatters::Abstract.new(@strings, { consume_comments: true })
119
+ formatter.set_comment_for_key('key', 'comment')
120
+
121
+ assert_equal 'comment', formatter.strings.strings_map['key'].comment
122
+ end
123
+ end
124
+
125
+ class CommentReference < TwineTestCase
126
+ def setup
127
+ super
128
+
129
+ @strings = build_twine_file 'en' do
130
+ add_section 'Section' do
131
+ add_row refkey: 'ref-value', comment: 'reference comment'
132
+ add_row key: 'value', ref: :refkey
133
+ end
134
+ end
135
+
136
+ @formatter = Twine::Formatters::Abstract.new(@strings, { consume_comments: true })
137
+ end
138
+
139
+ def test_set_comment_does_not_add_unchanged_comment
140
+ @formatter.set_comment_for_key 'key', 'reference comment'
141
+
142
+ assert_nil @strings.strings_map['key'].raw_comment
143
+ end
144
+
145
+ def test_set_comment_adds_changed_comment
146
+ @formatter.set_comment_for_key 'key', 'changed comment'
147
+
148
+ assert_equal 'changed comment', @strings.strings_map['key'].raw_comment
149
+ end
150
+ end
151
+
152
+ end