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