twine 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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