urbane 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -0
- data/README.md +68 -0
- data/Rakefile +13 -0
- data/lib/urbane/vendor/ordered_hash.rb +224 -0
- data/lib/urbane/version.rb +3 -0
- data/lib/urbane.rb +86 -0
- data/test/fixtures/google_spreadsheet_response.json +111 -0
- data/test/fixtures/google_worksheet_response.json +220 -0
- data/test/test_helper.rb +8 -0
- data/test/urbane_test.rb +64 -0
- data/urbane.gemspec +28 -0
- metadata +117 -0
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# README
|
2
|
+
|
3
|
+
Read a google spreadsheet and generate translation files into a target
|
4
|
+
directory as JSON files. It uses the googles JSON api as opposed to the XML api. That means that one has to publish the doc in order to get the spreadsheet id. At some point, it makes sense to use the more powerfull XML API using credentials to load the spreadsheet.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
gem install urbane
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
require 'urbane'
|
13
|
+
|
14
|
+
spreadsheet_id = '0Amfbd9df0sdflkewsd09dsfkl328sdf02'
|
15
|
+
target_dir = '/tmp/translations'
|
16
|
+
file_name = 'text_ids.json'
|
17
|
+
|
18
|
+
# the keys define the column headers in the spreadsheet
|
19
|
+
# the values define the name of the folder for a given language
|
20
|
+
languages = {
|
21
|
+
:english => 'en',
|
22
|
+
:german => 'de',
|
23
|
+
:french => 'fr',
|
24
|
+
:italian => 'it',
|
25
|
+
:turkish => 'tr',
|
26
|
+
:spanish => 'es',
|
27
|
+
:portuguese => 'pt'
|
28
|
+
}
|
29
|
+
|
30
|
+
Urbane::Generator.new({
|
31
|
+
:spreadsheet_id => spreadsheet_id,
|
32
|
+
:target_dir => target_dir,
|
33
|
+
:file_name => file_name,
|
34
|
+
:languages => languages,
|
35
|
+
:fallback_language => :english
|
36
|
+
}).run
|
37
|
+
|
38
|
+
## Output format
|
39
|
+
|
40
|
+
For now it only supports JSON
|
41
|
+
|
42
|
+
{
|
43
|
+
"sun_intro_step2"=>"Build another one…",
|
44
|
+
"sun_intro_step3"=>"... and another one."
|
45
|
+
}
|
46
|
+
|
47
|
+
## Output structure
|
48
|
+
|
49
|
+
target_dir
|
50
|
+
- en
|
51
|
+
- text_ids.json
|
52
|
+
- fr
|
53
|
+
- text_ids.json
|
54
|
+
|
55
|
+
## Requirements
|
56
|
+
|
57
|
+
### A google spreadsheet
|
58
|
+
|
59
|
+
For now it needs to have a certain format. Check the [demo document](https://docs.google.com/spreadsheet/ccc?key=0Auo5c2PWMqR4dHlOSjlXcjY0X01udzNPdHlKZ09QTVE&hl=en_US). You have several sheets in one document
|
60
|
+
|
61
|
+
|
62
|
+
### A google spreadsheet id
|
63
|
+
|
64
|
+
1. Create a spreadsheet on google docs
|
65
|
+
2. Click on File -> Publish To The Web
|
66
|
+
3. Check 'Automatically republish when changes are made'
|
67
|
+
4. Find the spreadsheet id in the generated link. It is marked with 'key='. In the following url, the key would be '0Auo5c2PWMqR4dHlOSjlXcjY0X01udzNPdHlKZ09QTVE': https://docs.google.com/spreadsheet/pub?hl=en_US&hl=en_US&key=0Auo5c2PWMqR4dHlOSjlXcjY0X01udzNPdHlKZ09QTVE&output=html
|
68
|
+
5. Click on the close button
|
data/Rakefile
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
module Urbane
|
2
|
+
begin
|
3
|
+
require 'psych'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
YAML.add_builtin_type("omap") do |type, val|
|
10
|
+
ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }]
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActiveSupport
|
14
|
+
# The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
|
15
|
+
# order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
|
16
|
+
# implements a hash that preserves insertion order, as in Ruby 1.9:
|
17
|
+
#
|
18
|
+
# oh = ActiveSupport::OrderedHash.new
|
19
|
+
# oh[:a] = 1
|
20
|
+
# oh[:b] = 2
|
21
|
+
# oh.keys # => [:a, :b], this order is guaranteed
|
22
|
+
#
|
23
|
+
# <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
|
24
|
+
class OrderedHash < ::Hash #:nodoc:
|
25
|
+
def to_yaml_type
|
26
|
+
"!tag:yaml.org,2002:omap"
|
27
|
+
end
|
28
|
+
|
29
|
+
def encode_with(coder)
|
30
|
+
coder.represent_seq '!omap', map { |k,v| { k => v } }
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_yaml(opts = {})
|
34
|
+
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
|
35
|
+
return super
|
36
|
+
end
|
37
|
+
|
38
|
+
YAML.quick_emit(self, opts) do |out|
|
39
|
+
out.seq(taguri) do |seq|
|
40
|
+
each do |k, v|
|
41
|
+
seq.add(k => v)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def nested_under_indifferent_access
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt>
|
52
|
+
def extractable_options?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Hash is ordered in Ruby 1.9!
|
57
|
+
if RUBY_VERSION < '1.9'
|
58
|
+
|
59
|
+
# In MRI the Hash class is core and written in C. In particular, methods are
|
60
|
+
# programmed with explicit C function calls and polymorphism is not honored.
|
61
|
+
#
|
62
|
+
# For example, []= is crucial in this implementation to maintain the @keys
|
63
|
+
# array but hash.c invokes rb_hash_aset() originally. This prevents method
|
64
|
+
# reuse through inheritance and forces us to reimplement stuff.
|
65
|
+
#
|
66
|
+
# For instance, we cannot use the inherited #merge! because albeit the algorithm
|
67
|
+
# itself would work, our []= is not being called at all by the C code.
|
68
|
+
|
69
|
+
def initialize(*args, &block)
|
70
|
+
super
|
71
|
+
@keys = []
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.[](*args)
|
75
|
+
ordered_hash = new
|
76
|
+
|
77
|
+
if (args.length == 1 && args.first.is_a?(Array))
|
78
|
+
args.first.each do |key_value_pair|
|
79
|
+
next unless (key_value_pair.is_a?(Array))
|
80
|
+
ordered_hash[key_value_pair[0]] = key_value_pair[1]
|
81
|
+
end
|
82
|
+
|
83
|
+
return ordered_hash
|
84
|
+
end
|
85
|
+
|
86
|
+
unless (args.size % 2 == 0)
|
87
|
+
raise ArgumentError.new("odd number of arguments for Hash")
|
88
|
+
end
|
89
|
+
|
90
|
+
args.each_with_index do |val, ind|
|
91
|
+
next if (ind % 2 != 0)
|
92
|
+
ordered_hash[val] = args[ind + 1]
|
93
|
+
end
|
94
|
+
|
95
|
+
ordered_hash
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize_copy(other)
|
99
|
+
super
|
100
|
+
# make a deep copy of keys
|
101
|
+
@keys = other.keys
|
102
|
+
end
|
103
|
+
|
104
|
+
def []=(key, value)
|
105
|
+
@keys << key unless has_key?(key)
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete(key)
|
110
|
+
if has_key? key
|
111
|
+
index = @keys.index(key)
|
112
|
+
@keys.delete_at index
|
113
|
+
end
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
def delete_if
|
118
|
+
super
|
119
|
+
sync_keys!
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def reject!
|
124
|
+
super
|
125
|
+
sync_keys!
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def reject(&block)
|
130
|
+
dup.reject!(&block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def keys
|
134
|
+
@keys.dup
|
135
|
+
end
|
136
|
+
|
137
|
+
def values
|
138
|
+
@keys.collect { |key| self[key] }
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_hash
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_a
|
146
|
+
@keys.map { |key| [ key, self[key] ] }
|
147
|
+
end
|
148
|
+
|
149
|
+
def each_key
|
150
|
+
return to_enum(:each_key) unless block_given?
|
151
|
+
@keys.each { |key| yield key }
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
def each_value
|
156
|
+
return to_enum(:each_value) unless block_given?
|
157
|
+
@keys.each { |key| yield self[key]}
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
def each
|
162
|
+
return to_enum(:each) unless block_given?
|
163
|
+
@keys.each {|key| yield [key, self[key]]}
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
def each_pair
|
168
|
+
return to_enum(:each_pair) unless block_given?
|
169
|
+
@keys.each {|key| yield key, self[key]}
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :select, :find_all
|
174
|
+
|
175
|
+
def clear
|
176
|
+
super
|
177
|
+
@keys.clear
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
def shift
|
182
|
+
k = @keys.first
|
183
|
+
v = delete(k)
|
184
|
+
[k, v]
|
185
|
+
end
|
186
|
+
|
187
|
+
def merge!(other_hash)
|
188
|
+
if block_given?
|
189
|
+
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
|
190
|
+
else
|
191
|
+
other_hash.each { |k, v| self[k] = v }
|
192
|
+
end
|
193
|
+
self
|
194
|
+
end
|
195
|
+
|
196
|
+
alias_method :update, :merge!
|
197
|
+
|
198
|
+
def merge(other_hash, &block)
|
199
|
+
dup.merge!(other_hash, &block)
|
200
|
+
end
|
201
|
+
|
202
|
+
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
|
203
|
+
def replace(other)
|
204
|
+
super
|
205
|
+
@keys = other.keys
|
206
|
+
self
|
207
|
+
end
|
208
|
+
|
209
|
+
def invert
|
210
|
+
OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
|
211
|
+
end
|
212
|
+
|
213
|
+
def inspect
|
214
|
+
"#<OrderedHash #{super}>"
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
def sync_keys!
|
219
|
+
@keys.delete_if {|k| !has_key?(k)}
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
data/lib/urbane.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "urbane/version"
|
2
|
+
require "urbane/vendor/ordered_hash"
|
3
|
+
require "json"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module Urbane
|
7
|
+
class Generator
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@target_dir = options[:target_dir]
|
11
|
+
@spreadsheet_id = options[:spreadsheet_id]
|
12
|
+
@file_name_for_translation_file = options[:file_name]
|
13
|
+
@language_locale_map = options[:languages]
|
14
|
+
@languages = @language_locale_map.keys
|
15
|
+
@fallback_language = options[:fallback_language]
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
@text_ids = {}
|
20
|
+
@languages.each do |language|
|
21
|
+
@text_ids[language] = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
process_spreadsheet
|
25
|
+
write_files
|
26
|
+
end
|
27
|
+
|
28
|
+
def google_spreadsheet_url
|
29
|
+
"http://spreadsheets.google.com/feeds/worksheets/#{@spreadsheet_id}/public/values/?alt=json"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def write_files
|
35
|
+
@language_locale_map.each do |language, locale|
|
36
|
+
`mkdir -p #{@target_dir}/#{locale}`
|
37
|
+
File.open("#{@target_dir}/#{locale}/#{@file_name_for_translation_file}", 'w') do |f|
|
38
|
+
f.write JSON.pretty_generate(sorted_hash_for_language(language))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def sorted_hash_for_language(language)
|
44
|
+
@text_ids[language].sort.inject(Urbane::ActiveSupport::OrderedHash.new) do |sorted_hash, translation|
|
45
|
+
sorted_hash[translation[0]] = translation[1]
|
46
|
+
sorted_hash
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_spreadsheet
|
51
|
+
worksheet_list.each do |entry|
|
52
|
+
rows_in_worksheet(entry).each do |row|
|
53
|
+
assign_ids_for_row(row)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def assign_ids_for_row(row)
|
59
|
+
key = (row['gsx$key'] || {})['$t'].to_s
|
60
|
+
unless key.empty?
|
61
|
+
@languages.each do |language|
|
62
|
+
value = (row["gsx$#{language.to_s}"] || {})['$t'].to_s
|
63
|
+
if value == ''
|
64
|
+
@text_ids[language][key] = row["gsx$#{@fallback_language}"]['$t']
|
65
|
+
else
|
66
|
+
@text_ids[language][key] = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def rows_in_worksheet(entry)
|
73
|
+
JSON.parse(open("#{url_for_worksheet(entry)}?alt=json"){|r| r.read})['feed']['entry'] || []
|
74
|
+
end
|
75
|
+
|
76
|
+
def url_for_worksheet(entry)
|
77
|
+
entry['link'].select{|link| link['rel'] == 'http://schemas.google.com/spreadsheets/2006#listfeed'}.first['href']
|
78
|
+
end
|
79
|
+
|
80
|
+
def worksheet_list
|
81
|
+
response = JSON.parse(open(google_spreadsheet_url){|r| r.read})
|
82
|
+
response['feed']['entry']
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
{
|
2
|
+
"encoding": "UTF-8",
|
3
|
+
"version": "1.0",
|
4
|
+
"feed": {
|
5
|
+
"xmlns$gs": "http://schemas.google.com/spreadsheets/2006",
|
6
|
+
"xmlns": "http://www.w3.org/2005/Atom",
|
7
|
+
|
8
|
+
"category": [
|
9
|
+
{
|
10
|
+
"term": "http://schemas.google.com/spreadsheets/2006#worksheet",
|
11
|
+
"scheme": "http://schemas.google.com/spreadsheets/2006"
|
12
|
+
}
|
13
|
+
],
|
14
|
+
"author": [
|
15
|
+
{
|
16
|
+
"name": {
|
17
|
+
"$t": "barbara.hellmann"
|
18
|
+
},
|
19
|
+
"email": {
|
20
|
+
"$t": "barbara.hellmann@wooga.net"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
],
|
24
|
+
"title": {
|
25
|
+
"$t": "PetsHouse_Translations",
|
26
|
+
"type": "text"
|
27
|
+
},
|
28
|
+
"link": [
|
29
|
+
{
|
30
|
+
"href": "http://spreadsheets.google.com/pub?key=0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc",
|
31
|
+
"rel": "alternate",
|
32
|
+
"type": "text/html"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"href": "http://spreadsheets.google.com/feeds/worksheets/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/public/values",
|
36
|
+
"rel": "http://schemas.google.com/g/2005#feed",
|
37
|
+
"type": "application/atom+xml"
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"href": "http://spreadsheets.google.com/feeds/worksheets/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/public/values?alt=json",
|
41
|
+
"rel": "self",
|
42
|
+
"type": "application/atom+xml"
|
43
|
+
}
|
44
|
+
],
|
45
|
+
"updated": {
|
46
|
+
"$t": "2010-07-14T08:07:58.884Z"
|
47
|
+
},
|
48
|
+
"openSearch$startIndex": {
|
49
|
+
"$t": "1"
|
50
|
+
},
|
51
|
+
"id": {
|
52
|
+
"$t": "http://spreadsheets.google.com/feeds/worksheets/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/public/values"
|
53
|
+
},
|
54
|
+
"openSearch$totalResults": {
|
55
|
+
"$t": "4"
|
56
|
+
},
|
57
|
+
"xmlns$openSearch": "http://a9.com/-/spec/opensearchrss/1.0/",
|
58
|
+
"entry": [
|
59
|
+
{
|
60
|
+
"category": [
|
61
|
+
{
|
62
|
+
"term": "http://schemas.google.com/spreadsheets/2006#worksheet",
|
63
|
+
"scheme": "http://schemas.google.com/spreadsheets/2006"
|
64
|
+
}
|
65
|
+
],
|
66
|
+
"title": {
|
67
|
+
"$t": "General",
|
68
|
+
"type": "text"
|
69
|
+
},
|
70
|
+
"gs$rowCount": {
|
71
|
+
"$t": "130"
|
72
|
+
},
|
73
|
+
"id": {
|
74
|
+
"$t": "http://spreadsheets.google.com/feeds/worksheets/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/public/values/od6"
|
75
|
+
},
|
76
|
+
"gs$colCount": {
|
77
|
+
"$t": "20"
|
78
|
+
},
|
79
|
+
"content": {
|
80
|
+
"$t": "General",
|
81
|
+
"type": "text"
|
82
|
+
},
|
83
|
+
"link": [
|
84
|
+
{
|
85
|
+
"href": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/od6/public/values",
|
86
|
+
"rel": "http://schemas.google.com/spreadsheets/2006#listfeed",
|
87
|
+
"type": "application/atom+xml"
|
88
|
+
},
|
89
|
+
{
|
90
|
+
"href": "http://spreadsheets.google.com/feeds/cells/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/od6/public/values",
|
91
|
+
"rel": "http://schemas.google.com/spreadsheets/2006#cellsfeed",
|
92
|
+
"type": "application/atom+xml"
|
93
|
+
},
|
94
|
+
{
|
95
|
+
"href": "http://spreadsheets.google.com/tq?key=0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc&sheet=od6&pub=1",
|
96
|
+
"rel": "http://schemas.google.com/visualization/2008#visualizationApi",
|
97
|
+
"type": "application/atom+xml"
|
98
|
+
},
|
99
|
+
{
|
100
|
+
"href": "http://spreadsheets.google.com/feeds/worksheets/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/public/values/od6",
|
101
|
+
"rel": "self",
|
102
|
+
"type": "application/atom+xml"
|
103
|
+
}
|
104
|
+
],
|
105
|
+
"updated": {
|
106
|
+
"$t": "2010-07-14T08:07:58.884Z"
|
107
|
+
}
|
108
|
+
}
|
109
|
+
]
|
110
|
+
}
|
111
|
+
}
|
@@ -0,0 +1,220 @@
|
|
1
|
+
{
|
2
|
+
"encoding": "UTF-8",
|
3
|
+
"version": "1.0",
|
4
|
+
"feed": {
|
5
|
+
"category": [
|
6
|
+
{
|
7
|
+
"term": "http://schemas.google.com/spreadsheets/2006#list",
|
8
|
+
"scheme": "http://schemas.google.com/spreadsheets/2006"
|
9
|
+
}
|
10
|
+
],
|
11
|
+
"author": [
|
12
|
+
{
|
13
|
+
"name": {
|
14
|
+
"$t": "barbara.hellmann"
|
15
|
+
},
|
16
|
+
"email": {
|
17
|
+
"$t": "barbara.hellmann@wooga.net"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"title": {
|
22
|
+
"$t": "Sun",
|
23
|
+
"type": "text"
|
24
|
+
},
|
25
|
+
"openSearch$startIndex": {
|
26
|
+
"$t": "1"
|
27
|
+
},
|
28
|
+
"id": {
|
29
|
+
"$t": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values"
|
30
|
+
},
|
31
|
+
"openSearch$totalResults": {
|
32
|
+
"$t": "28"
|
33
|
+
},
|
34
|
+
"xmlns$gsx": "http://schemas.google.com/spreadsheets/2006/extended",
|
35
|
+
"xmlns$openSearch": "http://a9.com/-/spec/opensearchrss/1.0/",
|
36
|
+
"entry": [
|
37
|
+
{
|
38
|
+
"category": [
|
39
|
+
{
|
40
|
+
"term": "http://schemas.google.com/spreadsheets/2006#list",
|
41
|
+
"scheme": "http://schemas.google.com/spreadsheets/2006"
|
42
|
+
}
|
43
|
+
],
|
44
|
+
"gsx$english": {
|
45
|
+
"$t": ""
|
46
|
+
},
|
47
|
+
"title": {
|
48
|
+
"$t": "Sun Intro",
|
49
|
+
"type": "text"
|
50
|
+
},
|
51
|
+
"gsx$description": {
|
52
|
+
"$t": ""
|
53
|
+
},
|
54
|
+
"gsx$section": {
|
55
|
+
"$t": "Sun Intro"
|
56
|
+
},
|
57
|
+
"gsx$turkish": {
|
58
|
+
"$t": ""
|
59
|
+
},
|
60
|
+
"id": {
|
61
|
+
"$t": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values/cokwr"
|
62
|
+
},
|
63
|
+
"content": {
|
64
|
+
"$t": "",
|
65
|
+
"type": "text"
|
66
|
+
},
|
67
|
+
"gsx$spanish": {
|
68
|
+
"$t": ""
|
69
|
+
},
|
70
|
+
"gsx$portuguese": {
|
71
|
+
"$t": ""
|
72
|
+
},
|
73
|
+
"link": [
|
74
|
+
{
|
75
|
+
"href": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values/cokwr",
|
76
|
+
"rel": "self",
|
77
|
+
"type": "application/atom+xml"
|
78
|
+
}
|
79
|
+
],
|
80
|
+
"gsx$french": {
|
81
|
+
"$t": ""
|
82
|
+
},
|
83
|
+
"gsx$key": {
|
84
|
+
"$t": ""
|
85
|
+
},
|
86
|
+
"updated": {
|
87
|
+
"$t": "2010-07-14T08:07:58.884Z"
|
88
|
+
},
|
89
|
+
"gsx$italian": {
|
90
|
+
"$t": ""
|
91
|
+
},
|
92
|
+
"gsx$german": {
|
93
|
+
"$t": ""
|
94
|
+
}
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"category": [
|
98
|
+
{
|
99
|
+
"term": "http://schemas.google.com/spreadsheets/2006#list",
|
100
|
+
"scheme": "http://schemas.google.com/spreadsheets/2006"
|
101
|
+
}
|
102
|
+
],
|
103
|
+
"gsx$english": {
|
104
|
+
"$t": "Build another one…"
|
105
|
+
},
|
106
|
+
"title": {
|
107
|
+
"$t": "Row: 4",
|
108
|
+
"type": "text"
|
109
|
+
},
|
110
|
+
"gsx$description": {
|
111
|
+
"$t": ""
|
112
|
+
},
|
113
|
+
"gsx$section": {
|
114
|
+
"$t": ""
|
115
|
+
},
|
116
|
+
"gsx$turkish": {
|
117
|
+
"$t": ""
|
118
|
+
},
|
119
|
+
"id": {
|
120
|
+
"$t": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values/cre1l"
|
121
|
+
},
|
122
|
+
"content": {
|
123
|
+
"$t": "key: sun_intro_step2, english: Build another one…, german: Baue einen weiteren Raum …",
|
124
|
+
"type": "text"
|
125
|
+
},
|
126
|
+
"gsx$spanish": {
|
127
|
+
"$t": ""
|
128
|
+
},
|
129
|
+
"gsx$portuguese": {
|
130
|
+
"$t": ""
|
131
|
+
},
|
132
|
+
"link": [
|
133
|
+
{
|
134
|
+
"href": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values/cre1l",
|
135
|
+
"rel": "self",
|
136
|
+
"type": "application/atom+xml"
|
137
|
+
}
|
138
|
+
],
|
139
|
+
"gsx$french": {
|
140
|
+
"$t": ""
|
141
|
+
},
|
142
|
+
"gsx$key": {
|
143
|
+
"$t": "sun_intro_step2"
|
144
|
+
},
|
145
|
+
"updated": {
|
146
|
+
"$t": "2010-07-14T08:07:58.884Z"
|
147
|
+
},
|
148
|
+
"gsx$italian": {
|
149
|
+
"$t": ""
|
150
|
+
},
|
151
|
+
"gsx$german": {
|
152
|
+
"$t": "Baue einen weiteren Raum … mit vielen äÄöü"
|
153
|
+
}
|
154
|
+
},
|
155
|
+
{
|
156
|
+
"category": [
|
157
|
+
{
|
158
|
+
"term": "http://schemas.google.com/spreadsheets/2006#list",
|
159
|
+
"scheme": "http://schemas.google.com/spreadsheets/2006"
|
160
|
+
}
|
161
|
+
],
|
162
|
+
"gsx$english": {
|
163
|
+
"$t": "... and another one."
|
164
|
+
},
|
165
|
+
"title": {
|
166
|
+
"$t": "Row: 5",
|
167
|
+
"type": "text"
|
168
|
+
},
|
169
|
+
"gsx$description": {
|
170
|
+
"$t": ""
|
171
|
+
},
|
172
|
+
"gsx$section": {
|
173
|
+
"$t": ""
|
174
|
+
},
|
175
|
+
"gsx$turkish": {
|
176
|
+
"$t": ""
|
177
|
+
},
|
178
|
+
"id": {
|
179
|
+
"$t": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values/chk2m"
|
180
|
+
},
|
181
|
+
"content": {
|
182
|
+
"$t": "key: sun_intro_step3, english: ... and another one., german: … und noch einen.",
|
183
|
+
"type": "text"
|
184
|
+
},
|
185
|
+
"gsx$spanish": {
|
186
|
+
"$t": ""
|
187
|
+
},
|
188
|
+
"gsx$portuguese": {
|
189
|
+
"$t": ""
|
190
|
+
},
|
191
|
+
"link": [
|
192
|
+
{
|
193
|
+
"href": "http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/3/public/values/chk2m",
|
194
|
+
"rel": "self",
|
195
|
+
"type": "application/atom+xml"
|
196
|
+
}
|
197
|
+
],
|
198
|
+
"gsx$french": {
|
199
|
+
"$t": ""
|
200
|
+
},
|
201
|
+
"gsx$key": {
|
202
|
+
"$t": "sun_intro_step3"
|
203
|
+
},
|
204
|
+
"updated": {
|
205
|
+
"$t": "2010-07-14T08:07:58.884Z"
|
206
|
+
},
|
207
|
+
"gsx$italian": {
|
208
|
+
"$t": ""
|
209
|
+
},
|
210
|
+
"gsx$german": {
|
211
|
+
"$t": "… und noch einen."
|
212
|
+
}
|
213
|
+
}
|
214
|
+
],
|
215
|
+
"updated": {
|
216
|
+
"$t": "2010-07-14T08:07:58.884Z"
|
217
|
+
},
|
218
|
+
"xmlns": "http://www.w3.org/2005/Atom"
|
219
|
+
}
|
220
|
+
}
|
data/test/test_helper.rb
ADDED
data/test/urbane_test.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class Urbane::GeneratorTest < Test::Unit::TestCase
|
5
|
+
FIXTURE_FILE_PATH = File.join('test', 'fixtures')
|
6
|
+
TARGET_DIR = File.join('/tmp', 'translation_generator_test')
|
7
|
+
|
8
|
+
context 'generator' do
|
9
|
+
setup do
|
10
|
+
FileUtils.mkdir_p(TARGET_DIR)
|
11
|
+
response = File.open(File.join(FIXTURE_FILE_PATH,'google_spreadsheet_response.json')){|f| f.read}
|
12
|
+
FakeWeb.register_uri(:get,
|
13
|
+
"http://spreadsheets.google.com/feeds/worksheets/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/public/values/?alt=json",
|
14
|
+
:body => response
|
15
|
+
)
|
16
|
+
|
17
|
+
response = File.open(File.join(FIXTURE_FILE_PATH,'google_worksheet_response.json')){|f| f.read}
|
18
|
+
FakeWeb.register_uri(:get,
|
19
|
+
"http://spreadsheets.google.com/feeds/list/0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc/od6/public/values?alt=json",
|
20
|
+
:body => response)
|
21
|
+
|
22
|
+
|
23
|
+
@options = {
|
24
|
+
:spreadsheet_id => '0AmfBdooXTXfQdHBRRkstNlJsdkpkaVVUdU5JTm1RZmc',
|
25
|
+
:target_dir => TARGET_DIR,
|
26
|
+
:file_name => 'text_ids.json',
|
27
|
+
:languages => {
|
28
|
+
:english => 'en',
|
29
|
+
:german => 'de',
|
30
|
+
:french => 'fr',
|
31
|
+
:italian => 'it',
|
32
|
+
:turkish => 'tr',
|
33
|
+
:spanish => 'es',
|
34
|
+
:portuguese => 'pt'
|
35
|
+
},
|
36
|
+
:fallback_language => :english
|
37
|
+
}
|
38
|
+
@generator = Urbane::Generator.new(@options)
|
39
|
+
@generator.run
|
40
|
+
end
|
41
|
+
|
42
|
+
teardown do
|
43
|
+
FileUtils.rm_rf(TARGET_DIR)
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'create a folder and a document for each locale' do
|
47
|
+
@options[:languages].values.each do |locale|
|
48
|
+
expected_file = File.join(TARGET_DIR, locale,'text_ids.json')
|
49
|
+
assert File.exists?(expected_file), "file #{expected_file} should exist"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
should 'fall back if a key is empty' do
|
54
|
+
info_hash_fr = JSON.parse(File.open(File.join(TARGET_DIR,'fr', 'text_ids.json'), "r"){ |f| f.read })
|
55
|
+
info_hash_us = JSON.parse(File.open(File.join(TARGET_DIR,'en', 'text_ids.json'), "r"){ |f| f.read })
|
56
|
+
assert_equal info_hash_us['sun_intro_step2'], info_hash_fr['sun_intro_step2']
|
57
|
+
end
|
58
|
+
|
59
|
+
should 'handle special chars' do
|
60
|
+
info_hash_de = JSON.parse(File.open(File.join(TARGET_DIR,'de', 'text_ids.json'), "r"){ |f| f.read })
|
61
|
+
assert info_hash_de['sun_intro_step2'].include?('äÄö')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/urbane.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "urbane/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "urbane"
|
7
|
+
s.version = Urbane::VERSION
|
8
|
+
s.authors = ["Patrick Huesler"]
|
9
|
+
s.email = ["patrick.huesler@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/phuesler/urbane"
|
11
|
+
s.summary = %q{Read google spreadsheet and generate translation files out of it}
|
12
|
+
s.description = %q{Read google spreadsheet and generate translation files out of it}
|
13
|
+
|
14
|
+
s.rubyforge_project = "urbane"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "mocha"
|
23
|
+
s.add_development_dependency "shoulda-context"
|
24
|
+
s.add_development_dependency "fakeweb"
|
25
|
+
s.add_development_dependency "rake"
|
26
|
+
|
27
|
+
s.add_runtime_dependency "json", "1.6.1"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: urbane
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Patrick Huesler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mocha
|
16
|
+
requirement: &70255489866860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70255489866860
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: shoulda-context
|
27
|
+
requirement: &70255489866240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70255489866240
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: fakeweb
|
38
|
+
requirement: &70255489865520 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70255489865520
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70255489864700 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70255489864700
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: json
|
60
|
+
requirement: &70255489863920 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - =
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.6.1
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70255489863920
|
69
|
+
description: Read google spreadsheet and generate translation files out of it
|
70
|
+
email:
|
71
|
+
- patrick.huesler@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- CHANGELOG.md
|
78
|
+
- Gemfile
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/urbane.rb
|
82
|
+
- lib/urbane/vendor/ordered_hash.rb
|
83
|
+
- lib/urbane/version.rb
|
84
|
+
- test/fixtures/google_spreadsheet_response.json
|
85
|
+
- test/fixtures/google_worksheet_response.json
|
86
|
+
- test/test_helper.rb
|
87
|
+
- test/urbane_test.rb
|
88
|
+
- urbane.gemspec
|
89
|
+
homepage: https://github.com/phuesler/urbane
|
90
|
+
licenses: []
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project: urbane
|
109
|
+
rubygems_version: 1.8.6
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Read google spreadsheet and generate translation files out of it
|
113
|
+
test_files:
|
114
|
+
- test/fixtures/google_spreadsheet_response.json
|
115
|
+
- test/fixtures/google_worksheet_response.json
|
116
|
+
- test/test_helper.rb
|
117
|
+
- test/urbane_test.rb
|