urbane 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.rbc
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # CHANGELOG
2
+
3
+ ## v0.0.1
4
+
5
+ The first working version. Rough around the edges. Assumes a certain
6
+ structure of the google spreadsheet and only supports one output format
7
+ in JSON.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in urbane.gemspec
4
+ gemspec
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,13 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task :default => ['test:units']
5
+
6
+ namespace :test do
7
+ Rake::TestTask.new(:units) do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/*_test.rb']
10
+
11
+ t.verbose = true
12
+ end
13
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Urbane
2
+ VERSION = "0.0.1"
3
+ 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
+ }
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift( File.join( File.dirname(__FILE__), "..", "lib") )
2
+
3
+ require "rubygems"
4
+ require 'test/unit'
5
+ require 'mocha'
6
+ require 'shoulda-context'
7
+ require 'fakeweb'
8
+ require 'urbane'
@@ -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