twine 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +10 -0
- data/lib/twine/formatters.rb +2 -1
- data/lib/twine/formatters/abstract.rb +15 -38
- data/lib/twine/formatters/android.rb +1 -1
- data/lib/twine/formatters/django.rb +143 -0
- data/lib/twine/formatters/gettext.rb +24 -14
- data/lib/twine/stringsfile.rb +1 -1
- data/lib/twine/version.rb +1 -1
- data/test/fixtures/en-2.po +23 -0
- data/test/fixtures/test-output-7.txt +2 -0
- data/test/fixtures/test-output-9.txt +21 -0
- data/test/twine_test.rb +8 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3466fcfcd027a2fd2af846ddd8bdba4d822cd39
|
4
|
+
data.tar.gz: 7f5df06f8206324c53d5e23de67aa8e35d90a6aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04dbdfb83e0ba690b5893d6b52539ae65b2ad687a6143fb7f998cacb6671418ff8ecedb0731ccf0404cc5f56b93b68ac2c1459ced44ddf840551ceea351cffcf
|
7
|
+
data.tar.gz: a9e1c41cdcea98f785eff464dde70f2883ea3b9080e40fce4271bbe429faafa98c1a756820d8fba189d560bc6b9e7751555b50ea0412ccfdd12e7247933ecdfb
|
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
gemspec
|
data/README.md
CHANGED
@@ -76,6 +76,7 @@ Twine currently supports the following formats for outputting strings:
|
|
76
76
|
* [Android String Resources][androidstrings] (format: android)
|
77
77
|
* [Gettext PO Files][gettextpo] (format: gettext)
|
78
78
|
* [jquery-localize Language Files][jquerylocalize] (format: jquery)
|
79
|
+
* [Django PO Files][djangopo] (format: django)
|
79
80
|
|
80
81
|
If you would like to enable twine to create language files in another format, create an appropriate formatter in `lib/twine/formatters`.
|
81
82
|
|
@@ -157,15 +158,23 @@ It is easy to incorporate Twine right into your iOS and OS X app build processes
|
|
157
158
|
|
158
159
|
Now, whenever you build your application, Xcode will automatically invoke Twine to make sure that your `.strings` files are up-to-date.
|
159
160
|
|
161
|
+
## User Interface
|
162
|
+
|
163
|
+
* [Twine TextMate 2 Bundle](https://github.com/mobiata/twine.tmbundle) — This [TextMate 2](https://github.com/textmate/textmate) bundle will make it easier for you to work with Twine strings files. In particular, it lets you use code folding to easily collapse and expand both strings and sections.
|
164
|
+
* [twine_ui](https://github.com/Daij-Djan/twine_ui) — A user interface for Twine written by [Dominik Pich](https://github.com/Daij-Djan/). Consider using this if you would prefer to use Twine without dropping to a command line.
|
165
|
+
|
160
166
|
## Contributors
|
161
167
|
|
162
168
|
Many thanks to all of the contributors to the Twine project, including:
|
163
169
|
|
170
|
+
* [Blake Watters](https://github.com/blakewatters)
|
164
171
|
* [Ishitoya Kentaro](https://github.com/kent013)
|
172
|
+
* [Joseph Earl](https://github.com/JosephEarl)
|
165
173
|
* [Kevin Everets](https://github.com/keverets)
|
166
174
|
* [Kevin Wood](https://github.com/kwood)
|
167
175
|
* [Mohammad Hejazi](https://github.com/MohammadHejazi)
|
168
176
|
* [Robert Guo](http://www.robertguo.me/)
|
177
|
+
* [Shai Shamir](https://github.com/pichirichi)
|
169
178
|
|
170
179
|
|
171
180
|
[rubyzip]: http://rubygems.org/gems/rubyzip
|
@@ -175,3 +184,4 @@ Many thanks to all of the contributors to the Twine project, including:
|
|
175
184
|
[androidstrings]: http://developer.android.com/guide/topics/resources/string-resource.html
|
176
185
|
[gettextpo]: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
|
177
186
|
[jquerylocalize]: https://github.com/coderifous/jquery-localize
|
187
|
+
[djangopo]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/
|
data/lib/twine/formatters.rb
CHANGED
@@ -4,9 +4,10 @@ require 'twine/formatters/apple'
|
|
4
4
|
require 'twine/formatters/flash'
|
5
5
|
require 'twine/formatters/gettext'
|
6
6
|
require 'twine/formatters/jquery'
|
7
|
+
require 'twine/formatters/django'
|
7
8
|
|
8
9
|
module Twine
|
9
10
|
module Formatters
|
10
|
-
FORMATTERS = [Formatters::Apple, Formatters::Android, Formatters::Gettext, Formatters::JQuery, Formatters::Flash]
|
11
|
+
FORMATTERS = [Formatters::Apple, Formatters::Android, Formatters::Gettext, Formatters::JQuery, Formatters::Flash, Formatters::Django]
|
11
12
|
end
|
12
13
|
end
|
@@ -14,42 +14,8 @@ module Twine
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def iosify_substitutions(str)
|
17
|
-
#
|
17
|
+
# use "@" instead of "s" for substituting strings
|
18
18
|
str.gsub!(/%([0-9\$]*)s/, '%\1@')
|
19
|
-
|
20
|
-
# 2) if substitutions are numbered, see if we can remove the numbering safely
|
21
|
-
expectedSub = 1
|
22
|
-
startFound = false
|
23
|
-
foundSub = 0
|
24
|
-
str.each_char do |c|
|
25
|
-
if startFound
|
26
|
-
if c == "%"
|
27
|
-
# this is a literal %, keep moving
|
28
|
-
startFound = false
|
29
|
-
elsif c.match(/\d/)
|
30
|
-
foundSub *= 10
|
31
|
-
foundSub += Integer(c)
|
32
|
-
elsif c == "$"
|
33
|
-
if expectedSub == foundSub
|
34
|
-
# okay to keep going
|
35
|
-
startFound = false
|
36
|
-
expectedSub += 1
|
37
|
-
else
|
38
|
-
# the numbering appears to be important (or non-existent), leave it alone
|
39
|
-
return str
|
40
|
-
end
|
41
|
-
end
|
42
|
-
elsif c == "%"
|
43
|
-
startFound = true
|
44
|
-
foundSub = 0
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# if we got this far, then the numbering (if any) is in order left-to-right and safe to remove
|
49
|
-
if expectedSub > 1
|
50
|
-
str.gsub!(/%\d+\$(.)/, '%\1')
|
51
|
-
end
|
52
|
-
|
53
19
|
return str
|
54
20
|
end
|
55
21
|
|
@@ -152,12 +118,23 @@ module Twine
|
|
152
118
|
end
|
153
119
|
|
154
120
|
file_name = @options[:file_name] || default_file_name
|
121
|
+
langs_written = []
|
155
122
|
Dir.foreach(path) do |item|
|
156
|
-
|
157
|
-
|
158
|
-
|
123
|
+
if item == "." or item == ".."
|
124
|
+
next
|
125
|
+
end
|
126
|
+
item = File.join(path, item)
|
127
|
+
if File.directory?(item)
|
128
|
+
lang = determine_language_given_path(item)
|
129
|
+
if lang
|
130
|
+
write_file(File.join(item, file_name), lang)
|
131
|
+
langs_written << lang
|
132
|
+
end
|
159
133
|
end
|
160
134
|
end
|
135
|
+
if langs_written.empty?
|
136
|
+
raise Twine::Error.new("Failed to genertate any files: No languages found at #{path}")
|
137
|
+
end
|
161
138
|
end
|
162
139
|
end
|
163
140
|
end
|
@@ -49,7 +49,7 @@ module Twine
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def read_file(path, lang)
|
52
|
-
resources_regex = /<resources>(.*)<\/resources>/m
|
52
|
+
resources_regex = /<resources(?:[^>]*)>(.*)<\/resources>/m
|
53
53
|
key_regex = /<string name="(\w+)">/
|
54
54
|
comment_regex = /<!-- (.*) -->/
|
55
55
|
value_regex = /<string name="\w+">(.*)<\/string>/
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Twine
|
2
|
+
module Formatters
|
3
|
+
class Django < Abstract
|
4
|
+
FORMAT_NAME = 'django'
|
5
|
+
EXTENSION = '.po'
|
6
|
+
DEFAULT_FILE_NAME = 'strings.po'
|
7
|
+
|
8
|
+
def self.can_handle_directory?(path)
|
9
|
+
Dir.entries(path).any? { |item| /^.+\.po$/.match(item) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_file_name
|
13
|
+
return DEFAULT_FILE_NAME
|
14
|
+
end
|
15
|
+
|
16
|
+
def determine_language_given_path(path)
|
17
|
+
path_arr = path.split(File::SEPARATOR)
|
18
|
+
path_arr.each do |segment|
|
19
|
+
match = /(..)\.po$/.match(segment)
|
20
|
+
if match
|
21
|
+
return match[1]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_file(path, lang)
|
29
|
+
comment_regex = /#.? *"(.*)"$/
|
30
|
+
key_regex = /msgid *"(.*)"$/
|
31
|
+
value_regex = /msgstr *"(.*)"$/m
|
32
|
+
|
33
|
+
encoding = Twine::Encoding.encoding_for_path(path)
|
34
|
+
sep = nil
|
35
|
+
if !encoding.respond_to?(:encode)
|
36
|
+
# This code is not necessary in 1.9.3 and does not work as it did in 1.8.7.
|
37
|
+
if encoding.end_with? 'LE'
|
38
|
+
sep = "\x0a\x00"
|
39
|
+
elsif encoding.end_with? 'BE'
|
40
|
+
sep = "\x00\x0a"
|
41
|
+
else
|
42
|
+
sep = "\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if encoding.index('UTF-16')
|
47
|
+
mode = "rb:#{encoding}"
|
48
|
+
else
|
49
|
+
mode = "r:#{encoding}"
|
50
|
+
end
|
51
|
+
|
52
|
+
File.open(path, mode) do |f|
|
53
|
+
last_comment = nil
|
54
|
+
while line = (sep) ? f.gets(sep) : f.gets
|
55
|
+
if encoding.index('UTF-16')
|
56
|
+
if line.respond_to? :encode!
|
57
|
+
line.encode!('UTF-8')
|
58
|
+
else
|
59
|
+
require 'iconv'
|
60
|
+
line = Iconv.iconv('UTF-8', encoding, line).join
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if @options[:consume_comments]
|
64
|
+
comment_match = comment_regex.match(line)
|
65
|
+
if comment_match
|
66
|
+
comment = comment_match[1]
|
67
|
+
end
|
68
|
+
else
|
69
|
+
comment = nil
|
70
|
+
end
|
71
|
+
key_match = key_regex.match(line)
|
72
|
+
if key_match
|
73
|
+
key = key_match[1].gsub('\\"', '"')
|
74
|
+
end
|
75
|
+
value_match = value_regex.match(line)
|
76
|
+
if value_match
|
77
|
+
value = value_match[1].gsub(/"\n"/, '').gsub('\\"', '"')
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
if key and key.length > 0 and value and value.length > 0
|
82
|
+
set_translation_for_key(key, lang, value)
|
83
|
+
if comment and comment.length > 0 and !comment.start_with?("--------- ")
|
84
|
+
set_comment_for_key(key, comment)
|
85
|
+
end
|
86
|
+
comment = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_file(path, lang)
|
94
|
+
default_lang = @strings.language_codes[0]
|
95
|
+
encoding = @options[:output_encoding] || 'UTF-8'
|
96
|
+
File.open(path, "w:#{encoding}") do |f|
|
97
|
+
f.puts "##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\n "
|
98
|
+
@strings.sections.each do |section|
|
99
|
+
printed_section = false
|
100
|
+
section.rows.each do |row|
|
101
|
+
if row.matches_tags?(@options[:tags], @options[:untagged])
|
102
|
+
f.puts ''
|
103
|
+
if !printed_section
|
104
|
+
if section.name && section.name.length > 0
|
105
|
+
f.print "#--------- #{section.name} ---------#\n\n"
|
106
|
+
end
|
107
|
+
printed_section = true
|
108
|
+
end
|
109
|
+
|
110
|
+
basetrans = row.translated_string_for_lang(default_lang)
|
111
|
+
|
112
|
+
key = row.key
|
113
|
+
key = key.gsub('"', '\\\\"')
|
114
|
+
|
115
|
+
value = row.translated_string_for_lang(lang, default_lang)
|
116
|
+
if value
|
117
|
+
value = value.gsub('"', '\\\\"')
|
118
|
+
|
119
|
+
comment = row.comment
|
120
|
+
|
121
|
+
if comment
|
122
|
+
comment = comment.gsub('"', '\\\\"')
|
123
|
+
end
|
124
|
+
|
125
|
+
if comment && comment.length > 0
|
126
|
+
f.print "#. #{comment} \n"
|
127
|
+
end
|
128
|
+
|
129
|
+
if basetrans && basetrans.length > 0
|
130
|
+
f.print "# base translation: \"#{basetrans}\"\n"
|
131
|
+
end
|
132
|
+
|
133
|
+
f.print "msgid \"#{key}\"\n"
|
134
|
+
f.print "msgstr \"#{value}\"\n"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Twine
|
2
4
|
module Formatters
|
3
5
|
class Gettext < Abstract
|
@@ -35,25 +37,24 @@ module Twine
|
|
35
37
|
value = nil
|
36
38
|
comment = nil
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
value = value_match[1].gsub('\\"', '"')
|
50
|
-
end
|
40
|
+
comment_match = comment_regex.match(item)
|
41
|
+
if comment_match
|
42
|
+
comment = comment_match[1]
|
43
|
+
end
|
44
|
+
key_match = key_regex.match(item)
|
45
|
+
if key_match
|
46
|
+
key = key_match[1].gsub('\\"', '"')
|
47
|
+
end
|
48
|
+
value_match = value_regex.match(item)
|
49
|
+
if value_match
|
50
|
+
value = value_match[1].gsub(/"\n"/, '').gsub('\\"', '"')
|
51
51
|
end
|
52
52
|
if key and key.length > 0 and value and value.length > 0
|
53
53
|
set_translation_for_key(key, lang, value)
|
54
|
-
if comment and comment.length > 0
|
54
|
+
if comment and comment.length > 0 and !comment.start_with?("SECTION:")
|
55
55
|
set_comment_for_key(key, comment)
|
56
56
|
end
|
57
|
+
comment = nil
|
57
58
|
end
|
58
59
|
end
|
59
60
|
end
|
@@ -68,6 +69,15 @@ module Twine
|
|
68
69
|
printed_section = false
|
69
70
|
section.rows.each do |row|
|
70
71
|
if row.matches_tags?(@options[:tags], @options[:untagged])
|
72
|
+
if !printed_section
|
73
|
+
f.puts ''
|
74
|
+
if section.name && section.name.length > 0
|
75
|
+
section_name = section.name.gsub('--', '—')
|
76
|
+
f.puts "# SECTION: #{section_name}"
|
77
|
+
end
|
78
|
+
printed_section = true
|
79
|
+
end
|
80
|
+
|
71
81
|
basetrans = row.translated_string_for_lang(default_lang)
|
72
82
|
|
73
83
|
if basetrans
|
data/lib/twine/stringsfile.rb
CHANGED
@@ -167,7 +167,7 @@ module Twine
|
|
167
167
|
end
|
168
168
|
@language_codes[1..-1].each do |lang|
|
169
169
|
value = row.translations[lang]
|
170
|
-
if value
|
170
|
+
if value
|
171
171
|
if value[0,1] == ' ' || value[-1,1] == ' ' || (value[0,1] == '`' && value[-1,1] == '`')
|
172
172
|
value = '`' + value + '`'
|
173
173
|
end
|
data/lib/twine/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
msgid ""
|
2
|
+
msgstr ""
|
3
|
+
"Language: en\n"
|
4
|
+
"X-Generator: Twine\n"
|
5
|
+
|
6
|
+
msgctxt "key1"
|
7
|
+
msgid "key1-english"
|
8
|
+
msgstr "key1-english"
|
9
|
+
|
10
|
+
msgctxt "key3"
|
11
|
+
msgid "key3-english"
|
12
|
+
msgstr ""
|
13
|
+
|
14
|
+
msgctxt "key4"
|
15
|
+
msgid "key4"
|
16
|
+
"multiline"
|
17
|
+
msgstr "A multi"
|
18
|
+
"line string\n"
|
19
|
+
"can occur"
|
20
|
+
|
21
|
+
msgctxt "key5"
|
22
|
+
msgid "A new string"
|
23
|
+
msgstr "A new string"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
[[Uncategorized]]
|
2
|
+
[key5]
|
3
|
+
en = A new string
|
4
|
+
|
5
|
+
[[My Strings]]
|
6
|
+
[key1]
|
7
|
+
en = key1-english
|
8
|
+
tags = tag1
|
9
|
+
comment = This is a comment
|
10
|
+
es = key1-spanish
|
11
|
+
fr = key1-french
|
12
|
+
[key2]
|
13
|
+
en = key2-english
|
14
|
+
tags = tag2
|
15
|
+
fr = key2-french
|
16
|
+
[key3]
|
17
|
+
en = key3-english
|
18
|
+
tags = tag1,tag2
|
19
|
+
es = key3-spanish
|
20
|
+
[key4]
|
21
|
+
en = A multiline string\ncan occur
|
data/test/twine_test.rb
CHANGED
@@ -84,6 +84,14 @@ class TwineTest < Test::Unit::TestCase
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
def test_consume_string_file_5
|
88
|
+
Dir.mktmpdir do |dir|
|
89
|
+
output_path = File.join(dir, 'strings.txt')
|
90
|
+
Twine::Runner.run(%W(consume-string-file test/fixtures/strings-1.txt test/fixtures/en-2.po -o #{output_path} -l en -a))
|
91
|
+
assert_equal(File.read('test/fixtures/test-output-9.txt'), File.read(output_path))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
87
95
|
def test_generate_report_1
|
88
96
|
Twine::Runner.run(%w(generate-report test/fixtures/strings-1.txt))
|
89
97
|
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: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- lib/twine/formatters/abstract.rb
|
57
57
|
- lib/twine/formatters/android.rb
|
58
58
|
- lib/twine/formatters/apple.rb
|
59
|
+
- lib/twine/formatters/django.rb
|
59
60
|
- lib/twine/formatters/flash.rb
|
60
61
|
- lib/twine/formatters/gettext.rb
|
61
62
|
- lib/twine/formatters/jquery.rb
|
@@ -68,6 +69,7 @@ files:
|
|
68
69
|
- test/fixtures/en-1.json
|
69
70
|
- test/fixtures/en-1.po
|
70
71
|
- test/fixtures/en-1.strings
|
72
|
+
- test/fixtures/en-2.po
|
71
73
|
- test/fixtures/fr-1.xml
|
72
74
|
- test/fixtures/strings-1.txt
|
73
75
|
- test/fixtures/strings-2.txt
|
@@ -80,6 +82,7 @@ files:
|
|
80
82
|
- test/fixtures/test-output-6.txt
|
81
83
|
- test/fixtures/test-output-7.txt
|
82
84
|
- test/fixtures/test-output-8.txt
|
85
|
+
- test/fixtures/test-output-9.txt
|
83
86
|
- test/twine_test.rb
|
84
87
|
homepage: https://github.com/mobiata/twine
|
85
88
|
licenses: []
|
@@ -100,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
103
|
version: '0'
|
101
104
|
requirements: []
|
102
105
|
rubyforge_project:
|
103
|
-
rubygems_version: 2.0.
|
106
|
+
rubygems_version: 2.0.3
|
104
107
|
signing_key:
|
105
108
|
specification_version: 4
|
106
109
|
summary: Manage strings and their translations for your iOS and Android projects.
|