Updated string resources generation tool

This commit is contained in:
Alex Zolotarev 2012-08-13 22:14:53 +03:00 committed by Alex Zolotarev
parent 42bb390828
commit 45732397c6
16 changed files with 186 additions and 117 deletions

View file

@ -1,6 +1,6 @@
# Twine
Twine is a command line tool for managing your strings and their translations. These strings are all stored in a master text file and then Twine uses this file to import and export strings in a variety of file types, including iOS and Mac OS X `.strings` files as well as Android `.xml` files. This allows individuals and companies to easily share strings across multiple projects, as well as export strings in any format the user wants.
Twine is a command line tool for managing your strings and their translations. These strings are all stored in a master text file and then Twine uses this file to import and export strings in a variety of file types, including iOS and Mac OS X `.strings` files, Android `.xml` files, and [jquery-localize][jquerylocalize] `.json` files. This allows individuals and companies to easily share strings across multiple projects, as well as export strings in any format the user wants.
## Install
@ -47,7 +47,7 @@ Whitepace in this file is mostly ignored. If you absolutely need to put spaces a
en = No
fr = Non
ja = いいえ
[[Errors]]
[path_not_found_error]
en = The file '%@' could not be found.
@ -57,7 +57,7 @@ Whitepace in this file is mostly ignored. If you absolutely need to put spaces a
en = The network is currently unavailable.
tags = app1
comment = An error describing when the device can not connect to the internet.
[[Escaping Example]]
[list_item_separator]
en = `, `
@ -68,10 +68,20 @@ Whitepace in this file is mostly ignored. If you absolutely need to put spaces a
tags = myothertag
comment = This string will evaluate to `%@`.
## Supported Output Formats
Twine currently supports the following formats for outputting strings:
* [iOS and OS X String Resources][applestrings] (format: apple)
* [Android String Resources][androidstrings] (format: android)
* [jquery-localize Language Files][jquerylocalize] (format: jquery)
If you would like to enable twine to create language files in another format, create an appropriate formatter in `lib/twine/formatters`.
## Usage
Usage: twine COMMAND STRINGS_FILE [INPUT_OR_OUTPUT_PATH] [--lang LANG1,LANG2...] [--tags TAG1,TAG2,TAG3...] [--format FORMAT]
### Commands
#### `generate-string-file`
@ -149,3 +159,6 @@ Now, whenever you build your application, Xcode will automatically invoke Twine
[rubyzip]: http://rubygems.org/gems/rubyzip
[git]: http://git-scm.org/
[INI]: http://en.wikipedia.org/wiki/INI_file
[applestrings]: http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/Strings/Strings.html
[androidstrings]: http://developer.android.com/guide/topics/resources/string-resource.html
[jquerylocalize]: https://github.com/coderifous/jquery-localize

View file

@ -13,6 +13,91 @@ module Twine
@options = options
end
def iosify_substitutions(str)
# 1) use "@" instead of "s" for substituting strings
str.gsub!(/%([0-9\$]*)s/, '%\1@')
# 2) if substitutions are numbered, see if we can remove the numbering safely
expectedSub = 1
startFound = false
foundSub = 0
str.each_char do |c|
if startFound
if c == "%"
# this is a literal %, keep moving
startFound = false
elsif c.match(/\d/)
foundSub *= 10
foundSub += Integer(c)
elsif c == "$"
if expectedSub == foundSub
# okay to keep going
startFound = false
expectedSub += 1
else
# the numbering appears to be important (or non-existent), leave it alone
return str
end
end
elsif c == "%"
startFound = true
foundSub = 0
end
end
# if we got this far, then the numbering (if any) is in order left-to-right and safe to remove
if expectedSub > 1
str.gsub!(/%\d+\$(.)/, '%\1')
end
return str
end
def androidify_substitutions(str)
# 1) use "s" instead of "@" for substituting strings
str.gsub!(/%([0-9\$]*)@/, '%\1s')
# 2) if there is more than one substitution in a string, make sure they are numbered
substituteCount = 0
startFound = false
str.each_char do |c|
if startFound
if c == "%"
# ignore as this is a literal %
elsif c.match(/\d/)
# leave the string alone if it already has numbered substitutions
return str
else
substituteCount += 1
end
startFound = false
elsif c == "%"
startFound = true
end
end
if substituteCount > 1
currentSub = 1
startFound = false
newstr = ""
str.each_char do |c|
if startFound
if !(c == "%")
newstr = newstr + "#{currentSub}$"
currentSub += 1
end
startFound = false
elsif c == "%"
startFound = true
end
newstr = newstr + c
end
return newstr
else
return str
end
end
def set_translation_for_key(key, lang, value)
if @strings.strings_map.include?(key)
@strings.strings_map[key].translations[lang] = value

View file

@ -49,19 +49,44 @@ module Twine
end
def read_file(path, lang)
resources_regex = /<resources>(.*)<\/resources>/m
key_regex = /<string name="(\w+)">/
comment_regex = /<!-- (.*) -->/
value_regex = /<string name="\w+">(.*)<\/string>/
key = nil
value = nil
comment = nil
File.open(path, 'r:UTF-8') do |f|
current_section = nil
doc = REXML::Document.new(f)
doc.elements.each('resources/string') do |ele|
key = ele.attributes["name"]
value = ele.text || ''
value.gsub!('\\\'', '\'')
value.gsub!('\\"', '"')
value.gsub!(/\n/, '')
value.gsub!('&lt;', '<')
value.gsub!('&amp;', '&')
value = iosify_substitutions(value)
set_translation_for_key(key, lang, value)
content_match = resources_regex.match(f.read)
if content_match
for line in content_match[1].split(/\r?\n/)
key_match = key_regex.match(line)
if key_match
key = key_match[1]
value_match = value_regex.match(line)
if value_match
value = value_match[1]
value.gsub!('\\"', '"')
value = iosify_substitutions(value)
else
value = ""
end
if @options[:tags]
set_tags_for_key(key, @options[:tags])
end
set_translation_for_key(key, lang, value)
if comment and comment.length > 0
set_comment_for_key(key, comment)
end
comment = nil
end
comment_match = comment_regex.match(line)
if comment_match
comment = comment_match[1]
end
end
end
end
end
@ -124,92 +149,6 @@ module Twine
f.puts '</resources>'
end
end
def iosify_substitutions(str)
# 1) use "@" instead of "s" for substituting strings
str.gsub!(/%([0-9\$]*)s/, '%\1@')
# 2) if substitutions are numbered, see if we can remove the numbering safely
expectedSub = 1
startFound = false
foundSub = 0
str.each_char do |c|
if startFound
if c == "%"
# this is a literal %, keep moving
startFound = false
elsif c.match(/\d/)
foundSub *= 10
foundSub += Integer(c)
elsif c == "$"
if expectedSub == foundSub
# okay to keep going
startFound = false
expectedSub += 1
else
# the numbering appears to be important (or non-existent), leave it alone
return str
end
end
elsif c == "%"
startFound = true
foundSub = 0
end
end
# if we got this far, then the numbering (if any) is in order left-to-right and safe to remove
if expectedSub > 1
str.gsub!(/%\d+\$(.)/, '%\1')
end
return str
end
def androidify_substitutions(str)
# 1) use "s" instead of "@" for substituting strings
str.gsub!(/%([0-9\$]*)@/, '%\1s')
# 2) if there is more than one substitution in a string, make sure they are numbered
substituteCount = 0
startFound = false
str.each_char do |c|
if startFound
if c == "%"
# ignore as this is a literal %
elsif c.match(/\d/)
# leave the string alone if it already has numbered substitutions
return str
else
substituteCount += 1
end
startFound = false
elsif c == "%"
startFound = true
end
end
if substituteCount > 1
currentSub = 1
startFound = false
newstr = ""
str.each_char do |c|
if startFound
if !(c == "%")
newstr = newstr + "#{currentSub}$"
currentSub += 1
end
startFound = false
elsif c == "%"
startFound = true
end
newstr = newstr + c
end
return newstr
else
return str
end
end
end
end
end

View file

@ -62,6 +62,7 @@ module Twine
key.gsub!('\\"', '"')
value = match[2]
value.gsub!('\\"', '"')
value = iosify_substitutions(value)
set_translation_for_key(key, lang, value)
if last_comment
set_comment_for_key(key, last_comment)
@ -100,19 +101,21 @@ module Twine
key = key.gsub('"', '\\\\"')
value = row.translated_string_for_lang(lang, default_lang)
value = value.gsub('"', '\\\\"')
if value
value = value.gsub('"', '\\\\"')
comment = row.comment
if comment
comment = comment.gsub('*/', '* /')
comment = row.comment
if comment
comment = comment.gsub('*/', '* /')
end
if comment && comment.length > 0
f.print "/* #{comment} */\n"
end
f.print "\"#{key}\" = \"#{value}\";\n"
end
if comment && comment.length > 0
f.print "/* #{comment} */\n"
end
f.print "\"#{key}\" = \"#{value}\";\n"
end
end
end
end
end

View file

@ -88,14 +88,14 @@ module Twine
if line.length > 4 && line[0, 2] == '[['
match = /^\[\[(.+)\]\]$/.match(line)
if match
current_section = StringsSection.new(match[1].strip)
current_section = StringsSection.new(match[1])
@sections << current_section
parsed = true
end
elsif line.length > 2 && line[0, 1] == '['
match = /^\[(.+)\]$/.match(line)
if match
current_row = StringsRow.new(match[1].strip)
current_row = StringsRow.new(match[1])
@strings_map[current_row.key] = current_row
if !current_section
current_section = StringsSection.new('')

View file

@ -1,3 +1,3 @@
module Twine
VERSION = '0.2.2'
VERSION = '0.3.1'
end

View file

@ -3,6 +3,7 @@
<!-- Generated by Twine -->
<!-- Language: fr -->
<resources>
<!-- This is a comment -->
<string name="key1">key1-french</string>
<string name="key2">key2-french</string>
<string name="key3">key3-french</string>

View file

@ -2,6 +2,7 @@
[key1]
en = key1-english
tags = tag1
comment = This is a comment
es = key1-spanish
fr = key1-french
[key2]

View file

@ -0,0 +1,5 @@
[[My Strings]]
[key with space ]
en = `string with space `
tags = tag1
comment = String ends with space

View file

@ -4,6 +4,7 @@
<!-- Language: fr -->
<resources>
<!-- My Strings -->
<!-- This is a comment -->
<string name="key1">key1-french</string>
<string name="key2">key2-french</string>
<string name="key3">key3-english</string>

View file

@ -6,6 +6,7 @@
/********** My Strings **********/
/* This is a comment */
"key1" = "key1-english";
"key3" = "key3-english";

View file

@ -2,6 +2,7 @@
[key1]
en = key1-english
tags = tag1
comment = This is a comment
es = key1-spanish
fr = key1-french
[key2]

View file

@ -6,6 +6,7 @@
[key1]
en = key1-english
tags = tag1
comment = This is a comment
es = key1-spanish
fr = key1-french
[key2]

View file

@ -6,6 +6,6 @@
{
/* My Strings */
"key1":"key1-english",
"key1":"key1-english", /* This is a comment */
"key3":"key3-english"
}

View file

@ -0,0 +1,10 @@
/**
* Apple Strings File
* Generated by Twine <%= Twine::VERSION %>
* Language: en
*/
/********** My Strings **********/
/* String ends with space */
"key with space " = "string with space ";

View file

@ -28,6 +28,14 @@ class TwineTest < Test::Unit::TestCase
end
end
def test_generate_string_file_4
Dir.mktmpdir do |dir|
output_path = File.join(dir, 'en.strings')
Twine::Runner.run(%W(generate-string-file test/fixtures/strings-2.txt #{output_path} -t tag1))
assert_equal(ERB.new(File.read('test/fixtures/test-output-6.txt')).result, File.read(output_path))
end
end
def test_consume_string_file_1
Dir.mktmpdir do |dir|
output_path = File.join(dir, 'strings.txt')