twine 1.0.2 → 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: 0e7971931654800b4d398dd6c3d9e86b96e4f5ba
4
- data.tar.gz: 64a1eccc6c604439e10c82bded869527b123f8db
2
+ SHA256:
3
+ metadata.gz: d8667f50ad7147b8b7edac0d82ce542c2ecc973604c8334e878513b6acaa9a14
4
+ data.tar.gz: 1ceb2b2b18dea8a3585bd91a1b427b30ebd9c888817dcf7140b3d211cd9e157c
5
5
  SHA512:
6
- metadata.gz: 808d4045076dfef7fcba6d26797eb05d7638fd3c2adc964c24059a20f378beb8c5d978f1598d651e1117a13a1fbdbdf102d21c767aee3a68c7eb5699fae7215a
7
- data.tar.gz: a63755c14cbadb14c0bba2960ff178a5785280bbb72e300dba5dc449ea510c823c6e248a1b848306f7c5de532c658ef8d4f034e80347f58ec2a4525673cf1ab6
6
+ metadata.gz: a961953ec7b74510c10e17114b84c227bac43117a869ba4254d7410a16ea9413e4133a2c9f099c3d67922fd6e1632d841f366e79d28687e61f73861721dea43a
7
+ data.tar.gz: 954b69b0d6e0fadec38cf145e0e1772eb1fde0259337b45ffac172d41b6f6c031b33373c768cfbb48ad3e9cfb1565dc9e91db8e825023421fdc30098439a4d86
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Twine
2
2
 
3
- [![Continuous Integration by CircleCI](https://circleci.com/gh/teespring/twine.svg?style=shield)](https://circleci.com/gh/teespring/twine)
3
+ [![Continuous Integration by CircleCI](https://circleci.com/gh/scelis/twine.svg?style=shield)](https://circleci.com/gh/scelis/twine)
4
4
 
5
5
  Twine is a command line tool for managing your strings and their translations. These are all stored in a master text file and then Twine uses this file to import and export localization files in a variety of types, including iOS and Mac OS X `.strings` files, Android `.xml` files, gettext `.po` files, and [jquery-localize][jquerylocalize] `.json` files. This allows individuals and companies to easily share translations across multiple projects, as well as export localization files in any format the user wants.
6
6
 
@@ -80,7 +80,7 @@ Twine currently supports the following output formats:
80
80
  * [Android String Resources][androidstrings] (format: android)
81
81
  * HTML tags will be escaped by replacing `<` with `&lt`
82
82
  * Tags inside `<![CDATA[` won't be escaped.
83
- * Supports [basic styling][androidstyling] with `<b>`, `<i>`, `<u>` and `<a>` links.
83
+ * Supports [basic styling][androidstyling] according to [Android documentation](https://developer.android.com/guide/topics/resources/string-resource.html#StylingWithHTML). All of the documented tags are supported, in addition to `<a>` links.
84
84
  * These tags will *not* be escaped if the string doesn't contain placeholders. You can reference them directly in your layouts or by using [`getText()`](https://developer.android.com/reference/android/content/res/Resources.html#getText(int)) to read them programatically.
85
85
  * These tags *will* be escaped if the string contains placeholders. You can use [`getString()`](https://developer.android.com/reference/android/content/res/Resources.html#getString(int,%20java.lang.Object...)) combined with [`fromHtml`](https://developer.android.com/reference/android/text/Html.html#fromHtml(java.lang.String)) as shown in the [documentation][androidstyling] to display them.
86
86
  * See [\#212](https://github.com/scelis/twine/issues/212) for details.
@@ -150,7 +150,7 @@ This command validates that the Twine data file can be parsed, contains no dupli
150
150
  The easiest way to create your first Twine data file is to run the [`consume-all-localization-files`](#consume-all-localization-files) command. The one caveat is to first create a blank file to use as your starting point. Then, just point the `consume-all-localization-files` command at a directory in your project containing all of your localization files.
151
151
 
152
152
  $ touch twine.txt
153
- $ twine consume-all-localization-files twine.txt Resources/Locales --developer-language en --consume-all --consume-comments
153
+ $ twine consume-all-localization-files twine.txt Resources/Locales --developer-language en --consume-all --consume-comments --format apple/android/gettext/jquery/django/tizen/flash
154
154
 
155
155
  ## Twine and Your Build Process
156
156
 
@@ -174,7 +174,10 @@ Now, whenever you build your application, Xcode will automatically invoke Twine
174
174
 
175
175
  ### Android Studio/Gradle
176
176
 
177
- Add the following task at the top level in app/build.gradle:
177
+ #### Standard
178
+
179
+ Add the following code to `app/build.gradle`:
180
+
178
181
  ```
179
182
  task generateLocalizations {
180
183
  String script = 'if hash twine 2>/dev/null; then twine generate-localization-file twine.txt ./src/main/res/values/generated_strings.xml; fi'
@@ -183,10 +186,46 @@ task generateLocalizations {
183
186
  args '-c', script
184
187
  }
185
188
  }
189
+
190
+ preBuild {
191
+ dependsOn generateLocalizations
192
+ }
186
193
  ```
187
194
 
188
- Now every time you build your app the localization files are generated from the Twine file.
195
+ #### Using [jruby](http://jruby.org)
189
196
 
197
+ With this approach, developers do not need to manually install ruby, gem, or twine.
198
+
199
+ Add the following code to `app/build.gradle`:
200
+
201
+ ```
202
+ buildscript {
203
+ repositories { jcenter() }
204
+
205
+ dependencies {
206
+ /* NOTE: Set your prefered version of jruby here. */
207
+ classpath "com.github.jruby-gradle:jruby-gradle-plugin:1.5.0"
208
+ }
209
+ }
210
+
211
+ apply plugin: 'com.github.jruby-gradle.base'
212
+
213
+ dependencies {
214
+ /* NOTE: Set your prefered version of twine here. */
215
+ jrubyExec 'rubygems:twine:1.0.3'
216
+ }
217
+
218
+ task generateLocalizations (type: JRubyExec) {
219
+ dependsOn jrubyPrepare
220
+ jrubyArgs '-S'
221
+ script "twine"
222
+ scriptArgs 'generate-localization-file', 'twine.txt', './src/main/res/values/generated_strings.xml'
223
+ }
224
+
225
+ preBuild {
226
+ dependsOn generateLocalizations
227
+ }
228
+ ```
190
229
 
191
230
  ## User Interface
192
231
 
@@ -224,4 +263,4 @@ Many thanks to all of the contributors to the Twine project, including:
224
263
  [djangopo]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/
225
264
  [tizen]: https://developer.tizen.org/documentation/articles/localization
226
265
  [flash]: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/resources/IResourceManager.html#getString()
227
- [printf]: https://en.wikipedia.org/wiki/Printf_format_string
266
+ [printf]: https://en.wikipedia.org/wiki/Printf_format_string
@@ -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.'
@@ -78,6 +86,10 @@ module Twine
78
86
  switch: ['-p', '--[no-]pedantic'],
79
87
  description: 'When validating a Twine file, perform additional checks that go beyond pure validity (like presence of tags).'
80
88
  },
89
+ quiet: {
90
+ switch: ['-q', '--[no-]quiet'],
91
+ description: 'Suppress all console output except error messages.'
92
+ },
81
93
  tags: {
82
94
  switch: ['-t', '--tags TAG1,TAG2,TAG3', Array],
83
95
  description: <<-DESC,
@@ -107,9 +119,11 @@ module Twine
107
119
  optional_options: [
108
120
  :developer_language,
109
121
  :encoding,
122
+ :escape_all_tags,
110
123
  :format,
111
124
  :include,
112
125
  :languages,
126
+ :quiet,
113
127
  :tags,
114
128
  :untagged,
115
129
  :validate
@@ -128,9 +142,11 @@ module Twine
128
142
  :create_folders,
129
143
  :developer_language,
130
144
  :encoding,
145
+ :escape_all_tags,
131
146
  :file_name,
132
147
  :format,
133
148
  :include,
149
+ :quiet,
134
150
  :tags,
135
151
  :untagged,
136
152
  :validate
@@ -146,7 +162,9 @@ module Twine
146
162
  optional_options: [
147
163
  :developer_language,
148
164
  :encoding,
165
+ :escape_all_tags,
149
166
  :include,
167
+ :quiet,
150
168
  :tags,
151
169
  :untagged,
152
170
  :validate
@@ -164,6 +182,7 @@ module Twine
164
182
  :format,
165
183
  :languages,
166
184
  :output_path,
185
+ :quiet,
167
186
  :tags
168
187
  ],
169
188
  option_validation: Proc.new { |options|
@@ -183,6 +202,7 @@ module Twine
183
202
  :encoding,
184
203
  :format,
185
204
  :output_path,
205
+ :quiet,
186
206
  :tags
187
207
  ],
188
208
  example: 'twine consume-all-localization-files twine.txt Resources/Locales/ --developer-language en --tags DefaultTag1,DefaultTag2'
@@ -197,6 +217,7 @@ module Twine
197
217
  :encoding,
198
218
  :format,
199
219
  :output_path,
220
+ :quiet,
200
221
  :tags
201
222
  ],
202
223
  example: 'twine consume-localization-archive twine.txt LocDrop5.zip'
@@ -206,7 +227,8 @@ module Twine
206
227
  arguments: [:twine_file],
207
228
  optional_options: [
208
229
  :developer_language,
209
- :pedantic
230
+ :pedantic,
231
+ :quiet
210
232
  ],
211
233
  example: 'twine validate-twine-file twine.txt'
212
234
  }
@@ -227,7 +249,7 @@ module Twine
227
249
 
228
250
  mapped_command = DEPRECATED_COMMAND_MAPPINGS[command]
229
251
  if mapped_command
230
- Twine::stderr.puts "WARNING: Twine commands names have changed. `#{command}` is now `#{mapped_command}`. The old command is deprecated and will soon stop working. For more information please check the documentation at https://github.com/mobiata/twine"
252
+ Twine::stdout.puts "WARNING: Twine commands names have changed. `#{command}` is now `#{mapped_command}`. The old command is deprecated and will soon stop working. For more information please check the documentation at https://github.com/mobiata/twine"
231
253
  command = mapped_command
232
254
  end
233
255
 
@@ -3,6 +3,8 @@ require 'fileutils'
3
3
  module Twine
4
4
  module Formatters
5
5
  class Abstract
6
+ LANGUAGE_CODE_WITH_OPTIONAL_REGION_CODE = "[a-z]{2}(?:-[A-Za-z]{2})?"
7
+
6
8
  attr_accessor :twine_file
7
9
  attr_accessor :options
8
10
 
@@ -38,7 +40,7 @@ module Twine
38
40
  definition.translations[lang] = value
39
41
  end
40
42
  elsif @options[:consume_all]
41
- Twine::stderr.puts "Adding new definition '#{key}' to twine file."
43
+ Twine::stdout.puts "Adding new definition '#{key}' to twine file."
42
44
  current_section = @twine_file.sections.find { |s| s.name == 'Uncategorized' }
43
45
  unless current_section
44
46
  current_section = TwineSection.new('Uncategorized')
@@ -54,7 +56,7 @@ module Twine
54
56
  @twine_file.definitions_by_key[key] = current_definition
55
57
  @twine_file.definitions_by_key[key].translations[lang] = value
56
58
  else
57
- Twine::stderr.puts "Warning: '#{key}' not found in twine file."
59
+ Twine::stdout.puts "WARNING: '#{key}' not found in twine file."
58
60
  end
59
61
  if !@twine_file.language_codes.include?(lang)
60
62
  @twine_file.add_language_code(lang)
@@ -76,7 +78,12 @@ module Twine
76
78
  end
77
79
 
78
80
  def determine_language_given_path(path)
79
- raise NotImplementedError.new("You must implement determine_language_given_path in your formatter class.")
81
+ only_language_and_region = /^#{LANGUAGE_CODE_WITH_OPTIONAL_REGION_CODE}$/i
82
+ basename = File.basename(path, File.extname(path))
83
+ return basename if basename =~ only_language_and_region
84
+ return basename if @twine_file.language_codes.include? basename
85
+
86
+ path.split(File::SEPARATOR).reverse.find { |segment| segment =~ only_language_and_region }
80
87
  end
81
88
 
82
89
  def output_path_for_language(lang)
@@ -37,11 +37,15 @@ module Twine
37
37
  end
38
38
  end
39
39
 
40
- return
40
+ return super
41
41
  end
42
42
 
43
43
  def output_path_for_language(lang)
44
- "values-#{lang}".gsub(/-(\p{Lu})/, '-r\1')
44
+ if lang == @twine_file.language_codes[0]
45
+ "values"
46
+ else
47
+ "values-#{lang}".gsub(/-(\p{Lu})/, '-r\1')
48
+ end
45
49
  end
46
50
 
47
51
  def set_translation_for_key(key, lang, value)
@@ -56,7 +60,7 @@ module Twine
56
60
 
57
61
  def read(io, lang)
58
62
  document = REXML::Document.new io, :compress_whitespace => %w{ string }
59
-
63
+ document.context[:attribute_quote] = :quote
60
64
  comment = nil
61
65
  document.root.children.each do |child|
62
66
  if child.is_a? REXML::Comment
@@ -67,7 +71,8 @@ module Twine
67
71
 
68
72
  key = child.attributes['name']
69
73
 
70
- set_translation_for_key(key, lang, child.text)
74
+ content = child.children.map(&:to_s).join
75
+ set_translation_for_key(key, lang, content)
71
76
  set_comment_for_key(key, comment) if comment
72
77
 
73
78
  comment = nil
@@ -108,22 +113,26 @@ module Twine
108
113
 
109
114
  # http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
110
115
  def escape_value(value)
111
- inside_cdata = /<\!\[CDATA\[((?!\]\]>).)*$/ # opening CDATA tag ('<![CDATA[') not followed by a closing tag (']]>')
112
- inside_opening_anchor_tag = /<a\s?((?!>).)*$/ # anchor tag start ('<a ') not followed by a '>'
116
+ inside_cdata = /<\!\[CDATA\[((?!\]\]>).)*$/ # opening CDATA tag ('<![CDATA[') not followed by a closing tag (']]>')
117
+ inside_opening_tag = /<(a|font|span|p)\s?((?!>).)*$/ # tag start ('<a ', '<font ', '<span ' or '<p ') not followed by a '>'
113
118
 
114
119
  # escape double and single quotes and & signs
115
- value = gsub_unless(value, '"', '\\"') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }
120
+ value = gsub_unless(value, '"', '\\"') { |substring| substring =~ inside_cdata || substring =~ inside_opening_tag }
116
121
  value = gsub_unless(value, "'", "\\'") { |substring| substring =~ inside_cdata }
117
- value = gsub_unless(value, /&/, '&amp;') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }
122
+ value = gsub_unless(value, /&/, '&amp;') { |substring| substring =~ inside_cdata || substring =~ inside_opening_tag }
118
123
 
119
124
  # if `value` contains a placeholder, escape all angle brackets
120
125
  # if not, escape opening angle brackes unless it's a supported styling tag
121
126
  # https://github.com/scelis/twine/issues/212
122
127
  # https://stackoverflow.com/questions/3235131/#18199543
123
- if number_of_twine_placeholders(value) > 0
124
- angle_bracket = /<(?!(\/?(\!\[CDATA)))/ # matches all `<` but <![CDATA
125
- else
126
- angle_bracket = /<(?!(\/?(b|u|i|a|\!\[CDATA)))/ # matches all `<` but <b>, <u>, <i>, <a> and <![CDATA
128
+ if number_of_twine_placeholders(value) > 0 or @options[:escape_all_tags]
129
+ # matches all `<` but <![CDATA
130
+ angle_bracket = /<(?!(\/?(\!\[CDATA)))/
131
+ else
132
+ # matches all '<' but <b>, <em>, <i>, <cite>, <dfn>, <big>, <small>, <font>, <tt>, <s>,
133
+ # <strike>, <del>, <u>, <super>, <sub>, <ul>, <li>, <br>, <div>, <span>, <p>, <a>
134
+ # and <![CDATA
135
+ angle_bracket = /<(?!(\/?(b|em|i|cite|dfn|big|small|font|tt|s|strike|del|u|super|sub|ul|li|br|div|span|p|a|\!\[CDATA)))/
127
136
  end
128
137
  value = gsub_unless(value, angle_bracket, '&lt;') { |substring| substring =~ inside_cdata }
129
138
 
@@ -30,7 +30,7 @@ module Twine
30
30
  end
31
31
  end
32
32
 
33
- return
33
+ return super
34
34
  end
35
35
 
36
36
  def output_path_for_language(lang)
@@ -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'
@@ -13,22 +14,11 @@ module Twine
13
14
  'strings.po'
14
15
  end
15
16
 
16
- def determine_language_given_path(path)
17
- path_arr = path.split(File::SEPARATOR)
18
- path_arr.each do |segment|
19
- match = /([a-z]{2}(-[A-Za-z]{2})?)\.po$/.match(segment)
20
- return match[1] if match
21
- end
22
-
23
- return
24
- end
25
-
26
17
  def read(io, lang)
27
- comment_regex = /#\. *"?(.*)"?$/
28
- key_regex = /msgid *"(.*)"$/
29
- value_regex = /msgstr *"(.*)"$/m
18
+ comment_regex = /^\s*#\. *"?(.*)"?$/
19
+ key_regex = /^msgid *"(.*)"$/
20
+ value_regex = /^msgstr *"(.*)"$/m
30
21
 
31
- last_comment = nil
32
22
  while line = io.gets
33
23
  comment_match = comment_regex.match(line)
34
24
  if comment_match
@@ -64,11 +54,12 @@ module Twine
64
54
  end
65
55
 
66
56
  def format_header(lang)
67
- "##\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\""
68
59
  end
69
60
 
70
61
  def format_section_header(section)
71
- "#--------- #{section.name} ---------#\n"
62
+ "# --------- #{section.name} --------- #\n"
72
63
  end
73
64
 
74
65
  def format_definition(definition, lang)
@@ -15,11 +15,6 @@ module Twine
15
15
  'resources.properties'
16
16
  end
17
17
 
18
- def determine_language_given_path(path)
19
- # match two-letter language code, optionally followed by a two letter region code
20
- path.split(File::SEPARATOR).reverse.find { |segment| segment =~ /^([a-z]{2}(-[a-z]{2})?)$/i }
21
- end
22
-
23
18
  def set_translation_for_key(key, lang, value)
24
19
  value = convert_placeholders_from_flash_to_twine(value)
25
20
  super(key, lang, value)
@@ -15,16 +15,6 @@ module Twine
15
15
  'strings.po'
16
16
  end
17
17
 
18
- def determine_language_given_path(path)
19
- path_arr = path.split(File::SEPARATOR)
20
- path_arr.each do |segment|
21
- match = /([a-z]{2}(-[A-Za-z]{2})?)\.po$/.match(segment)
22
- return match[1] if match
23
- end
24
-
25
- return
26
- end
27
-
28
18
  def read(io, lang)
29
19
  comment_regex = /#.? *"(.*)"$/
30
20
  key_regex = /msgctxt *"(.*)"$/
@@ -65,7 +55,7 @@ module Twine
65
55
  end
66
56
 
67
57
  def format_header(lang)
68
- "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Twine #{Twine::VERSION}\\n\"\n"
58
+ "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\"\n\"X-Generator: Twine #{Twine::VERSION}\"\n"
69
59
  end
70
60
 
71
61
  def format_section_header(section)
@@ -86,15 +76,15 @@ module Twine
86
76
  end
87
77
 
88
78
  def format_key(key)
89
- "msgctxt \"#{key}\"\n"
79
+ "msgctxt \"#{escape_quotes(key)}\"\n"
90
80
  end
91
81
 
92
82
  def format_base_translation(definition)
93
- "msgid \"#{definition.translations[@default_lang]}\"\n"
83
+ "msgid \"#{escape_quotes(definition.translations[@default_lang])}\"\n"
94
84
  end
95
85
 
96
86
  def format_value(value)
97
- "msgstr \"#{value}\"\n"
87
+ "msgstr \"#{escape_quotes(value)}\"\n"
98
88
  end
99
89
  end
100
90
  end
@@ -14,22 +14,17 @@ module Twine
14
14
  end
15
15
 
16
16
  def determine_language_given_path(path)
17
- path_arr = path.split(File::SEPARATOR)
18
- path_arr.each do |segment|
19
- match = /^((.+)-)?([^-]+)\.json$/.match(segment)
20
- if match
21
- return match[3]
22
- end
23
- end
17
+ match = /^.+-([^-]{2})\.json$/.match File.basename(path)
18
+ return match[1] if match
24
19
 
25
- return
20
+ return super
26
21
  end
27
22
 
28
23
  def read(io, lang)
29
24
  begin
30
25
  require "json"
31
26
  rescue LoadError
32
- raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
27
+ raise Twine::Error.new "You must run `gem install json` in order to read or write jquery-localize files."
33
28
  end
34
29
 
35
30
  json = JSON.load(io)
@@ -46,7 +41,7 @@ module Twine
46
41
 
47
42
  def format_sections(twine_file, lang)
48
43
  sections = twine_file.sections.map { |section| format_section(section, lang) }
49
- sections.delete_if &:empty?
44
+ sections.delete_if(&:empty?)
50
45
  sections.join(",\n\n")
51
46
  end
52
47