localio 0.1.7 → 0.2.1
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 +5 -5
- data/.github/workflows/ci.yml +28 -0
- data/.gitignore +4 -1
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Gemfile.lock +134 -0
- data/README.md +36 -34
- data/bin/localize +10 -7
- data/docs/plans/2026-02-23-modernization-design.md +91 -0
- data/docs/plans/2026-02-23-modernization.md +1699 -0
- data/docs/plans/2026-02-23-twine-writer-design.md +72 -0
- data/docs/plans/2026-02-23-twine-writer.md +267 -0
- data/lib/localio/localizable_writer.rb +4 -1
- data/lib/localio/processors/csv_processor.rb +1 -1
- data/lib/localio/processors/google_drive_processor.rb +19 -45
- data/lib/localio/processors/xlsx_processor.rb +1 -1
- data/lib/localio/template_handler.rb +3 -1
- data/lib/localio/templates/android_localizable.erb +14 -2
- data/lib/localio/templates/ios_constant_localizable.erb +16 -2
- data/lib/localio/templates/ios_localizable.erb +20 -5
- data/lib/localio/templates/java_properties_localizable.erb +16 -2
- data/lib/localio/templates/json_localizable.erb +6 -5
- data/lib/localio/templates/rails_localizable.erb +15 -3
- data/lib/localio/templates/resx_localizable.erb +14 -2
- data/lib/localio/templates/swift_constant_localizable.erb +15 -2
- data/lib/localio/version.rb +1 -1
- data/lib/localio/writers/ios_writer.rb +3 -3
- data/lib/localio/writers/swift_writer.rb +3 -3
- data/lib/localio/writers/twine_writer.rb +48 -0
- data/localio.gemspec +19 -25
- data/spec/fixtures/sample.csv +11 -0
- data/spec/localio/filter_spec.rb +40 -0
- data/spec/localio/formatter_spec.rb +32 -0
- data/spec/localio/processors/csv_processor_spec.rb +89 -0
- data/spec/localio/processors/google_drive_processor_spec.rb +107 -0
- data/spec/localio/processors/xls_processor_spec.rb +65 -0
- data/spec/localio/processors/xlsx_processor_spec.rb +59 -0
- data/spec/localio/segment_spec.rb +27 -0
- data/spec/localio/segments_list_holder_spec.rb +22 -0
- data/spec/localio/string_helper_spec.rb +49 -0
- data/spec/localio/template_handler_spec.rb +67 -0
- data/spec/localio/term_spec.rb +24 -0
- data/spec/localio/writers/android_writer_spec.rb +71 -0
- data/spec/localio/writers/ios_writer_spec.rb +63 -0
- data/spec/localio/writers/java_properties_writer_spec.rb +35 -0
- data/spec/localio/writers/json_writer_spec.rb +57 -0
- data/spec/localio/writers/rails_writer_spec.rb +47 -0
- data/spec/localio/writers/resx_writer_spec.rb +44 -0
- data/spec/localio/writers/swift_writer_spec.rb +42 -0
- data/spec/localio/writers/twine_writer_spec.rb +68 -0
- data/spec/localio_spec.rb +62 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/shared_terms.rb +35 -0
- metadata +61 -46
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Twine Writer Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-02-23
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Add a `:twine` platform writer that generates a [Twine](https://github.com/scelis/twine)-compatible `strings.txt` file containing all languages in a single file.
|
|
9
|
+
|
|
10
|
+
## Output Format
|
|
11
|
+
|
|
12
|
+
Standard Twine format with tab indentation. All languages are written per key, not per file.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
[[section_name]]
|
|
16
|
+
[key_name]
|
|
17
|
+
en = English value
|
|
18
|
+
es = Spanish value
|
|
19
|
+
comment = Optional comment
|
|
20
|
+
|
|
21
|
+
[another_key]
|
|
22
|
+
en = Another value
|
|
23
|
+
es = Otro valor
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Term Mapping
|
|
27
|
+
|
|
28
|
+
| Localio term | Twine output |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `[init-node]` | `[[section_name]]` (value from default language) |
|
|
31
|
+
| `[end-node]` | blank line (sections close implicitly) |
|
|
32
|
+
| `[comment]` | buffered; written as `comment = ...` under the next real key |
|
|
33
|
+
| regular key | `[key]` block with one `lang = value` line per language |
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
**Approach:** Pure Ruby writer (no ERB template). The comment-buffering logic and single-file-all-languages structure don't fit ERB well.
|
|
38
|
+
|
|
39
|
+
### Writer class
|
|
40
|
+
|
|
41
|
+
- **File:** `lib/localio/writers/twine_writer.rb`
|
|
42
|
+
- **Class:** `TwineWriter`
|
|
43
|
+
- **Interface:** `self.write(languages, terms, path, formatter, options)` — matches all existing writers
|
|
44
|
+
|
|
45
|
+
### Algorithm (single pass)
|
|
46
|
+
|
|
47
|
+
1. Open output file (`strings.txt` or `options[:output_file]`)
|
|
48
|
+
2. `pending_comment = nil`
|
|
49
|
+
3. For each term:
|
|
50
|
+
- `[comment]` → store `pending_comment` from default language value
|
|
51
|
+
- `[init-node]` → write `[[value]]`, reset `pending_comment`
|
|
52
|
+
- `[end-node]` → write blank line
|
|
53
|
+
- regular key → write `[key]` block with all lang translations; if `pending_comment` is set, append `\t\tcomment = ...` and clear it
|
|
54
|
+
|
|
55
|
+
### Platform registration
|
|
56
|
+
|
|
57
|
+
- Registered in `lib/localio.rb` as `:twine`
|
|
58
|
+
- Locfile usage: `platform :twine` or `platform :twine, :output_file => 'custom.txt'`
|
|
59
|
+
|
|
60
|
+
### Key formatting
|
|
61
|
+
|
|
62
|
+
- Smart formatter defaults to snake_case (consistent with android/rails)
|
|
63
|
+
|
|
64
|
+
## Testing
|
|
65
|
+
|
|
66
|
+
`spec/localio/writers/twine_writer_spec.rb` using the existing `standard terms` shared context and `Dir.mktmpdir` isolation. Cases:
|
|
67
|
+
|
|
68
|
+
- Creates `strings.txt` in the output path
|
|
69
|
+
- All languages present in each key block
|
|
70
|
+
- `[init-node]` produces `[[section]]` header
|
|
71
|
+
- `[comment]` row attaches as `comment = ...` to the following key
|
|
72
|
+
- `:output_file` option overrides the default filename
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Twine Writer Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a `:twine` platform writer that generates a single Twine-compatible `strings.txt` containing all languages.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Pure Ruby writer (no ERB template). Single-pass over terms: buffers `[comment]` rows and attaches them to the next real key, maps `[init-node]`/`[end-node]` to Twine `[[section]]` headers, writes all language translations per key. Registered in `localizable_writer.rb` alongside the existing 7 writers.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby 3.2+, RSpec 3.x, stdlib FileUtils
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Write the TwineWriter spec (failing)
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Create: `spec/localio/writers/twine_writer_spec.rb`
|
|
17
|
+
|
|
18
|
+
**Step 1: Create the spec file**
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
require 'localio/string_helper'
|
|
22
|
+
require 'localio/term'
|
|
23
|
+
require 'localio/formatter'
|
|
24
|
+
require 'localio/writers/twine_writer'
|
|
25
|
+
|
|
26
|
+
RSpec.describe TwineWriter do
|
|
27
|
+
include_context 'standard terms'
|
|
28
|
+
# standard terms provides: languages {'en'=>1,'es'=>2,'fr'=>3},
|
|
29
|
+
# default_language 'en', and terms:
|
|
30
|
+
# [comment] "Section General", app_name, greeting, dots_test, ampersand_test
|
|
31
|
+
|
|
32
|
+
let(:options) { { default_language: 'en' } }
|
|
33
|
+
|
|
34
|
+
describe '.write' do
|
|
35
|
+
it 'creates strings.txt in the output path' do
|
|
36
|
+
Dir.mktmpdir do |tmpdir|
|
|
37
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, terms, tmpdir, :smart, options) }
|
|
38
|
+
expect(File).to exist(File.join(tmpdir, 'strings.txt'))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'uses a custom filename when :output_file is specified' do
|
|
43
|
+
Dir.mktmpdir do |tmpdir|
|
|
44
|
+
Dir.chdir(tmpdir) do
|
|
45
|
+
TwineWriter.write(languages, terms, tmpdir, :smart, options.merge(output_file: 'translations.txt'))
|
|
46
|
+
end
|
|
47
|
+
expect(File).to exist(File.join(tmpdir, 'translations.txt'))
|
|
48
|
+
expect(File).not_to exist(File.join(tmpdir, 'strings.txt'))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'includes all languages for each key' do
|
|
53
|
+
Dir.mktmpdir do |tmpdir|
|
|
54
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, terms, tmpdir, :smart, options) }
|
|
55
|
+
content = File.read(File.join(tmpdir, 'strings.txt'))
|
|
56
|
+
expect(content).to include('en = My App')
|
|
57
|
+
expect(content).to include('es = Mi Aplicación')
|
|
58
|
+
expect(content).to include('fr = Mon Application')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'writes [init-node] terms as [[section]] headers' do
|
|
63
|
+
section_terms = [
|
|
64
|
+
Term.new('[init-node]').tap { |t| t.values['en'] = 'General'; t.values['es'] = 'General'; t.values['fr'] = 'General' },
|
|
65
|
+
Term.new('app_name').tap { |t| t.values['en'] = 'My App'; t.values['es'] = 'Mi App'; t.values['fr'] = 'Mon App' },
|
|
66
|
+
Term.new('[end-node]').tap { |t| t.values['en'] = 'end'; t.values['es'] = 'end'; t.values['fr'] = 'end' },
|
|
67
|
+
]
|
|
68
|
+
Dir.mktmpdir do |tmpdir|
|
|
69
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, section_terms, tmpdir, :smart, options) }
|
|
70
|
+
content = File.read(File.join(tmpdir, 'strings.txt'))
|
|
71
|
+
expect(content).to include('[[General]]')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'attaches [comment] value as comment = on the following key' do
|
|
76
|
+
Dir.mktmpdir do |tmpdir|
|
|
77
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, terms, tmpdir, :smart, options) }
|
|
78
|
+
content = File.read(File.join(tmpdir, 'strings.txt'))
|
|
79
|
+
# [comment] "Section General" appears before app_name and attaches to it
|
|
80
|
+
expect(content).to include("\t\tcomment = Section General")
|
|
81
|
+
# The comment block appears before the greeting key block
|
|
82
|
+
comment_pos = content.index('comment = Section General')
|
|
83
|
+
greeting_pos = content.index('[greeting]')
|
|
84
|
+
expect(comment_pos).to be < greeting_pos
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Step 2: Run the spec to confirm it fails with "uninitialized constant TwineWriter"**
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bundle exec rspec spec/localio/writers/twine_writer_spec.rb --format documentation 2>&1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Expected: `LoadError` or `NameError: uninitialized constant TwineWriter`
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Task 2: Implement TwineWriter
|
|
102
|
+
|
|
103
|
+
**Files:**
|
|
104
|
+
- Create: `lib/localio/writers/twine_writer.rb`
|
|
105
|
+
|
|
106
|
+
**Step 1: Create the writer**
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
require 'fileutils'
|
|
110
|
+
require 'localio/formatter'
|
|
111
|
+
|
|
112
|
+
class TwineWriter
|
|
113
|
+
def self.write(languages, terms, path, formatter, options)
|
|
114
|
+
puts 'Writing Twine translations...'
|
|
115
|
+
|
|
116
|
+
default_language = options[:default_language]
|
|
117
|
+
output_filename = options[:output_file] || 'strings.txt'
|
|
118
|
+
|
|
119
|
+
FileUtils.mkdir_p(path)
|
|
120
|
+
|
|
121
|
+
File.open(File.join(path, output_filename), 'w') do |f|
|
|
122
|
+
pending_comment = nil
|
|
123
|
+
|
|
124
|
+
terms.each do |term|
|
|
125
|
+
if term.is_comment?
|
|
126
|
+
pending_comment = term.values[default_language]
|
|
127
|
+
elsif term.keyword == '[init-node]'
|
|
128
|
+
f.puts "[[#{term.values[default_language]}]]"
|
|
129
|
+
pending_comment = nil
|
|
130
|
+
elsif term.keyword == '[end-node]'
|
|
131
|
+
f.puts ''
|
|
132
|
+
pending_comment = nil
|
|
133
|
+
else
|
|
134
|
+
key = Formatter.format(term.keyword, formatter, method(:twine_key_formatter))
|
|
135
|
+
f.puts "\t[#{key}]"
|
|
136
|
+
languages.keys.each do |lang|
|
|
137
|
+
f.puts "\t\t#{lang} = #{term.values[lang]}"
|
|
138
|
+
end
|
|
139
|
+
if pending_comment
|
|
140
|
+
f.puts "\t\tcomment = #{pending_comment}"
|
|
141
|
+
pending_comment = nil
|
|
142
|
+
end
|
|
143
|
+
f.puts ''
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
puts " > #{output_filename.yellow}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def self.twine_key_formatter(key)
|
|
154
|
+
key.space_to_underscore.strip_tag.downcase
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Step 2: Run the spec to confirm all 5 tests pass**
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
bundle exec rspec spec/localio/writers/twine_writer_spec.rb --format documentation 2>&1
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Expected: `5 examples, 0 failures`
|
|
166
|
+
|
|
167
|
+
**Step 3: Run the full suite to confirm no regressions**
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
bundle exec rspec --format progress 2>&1 | tail -5
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Expected: `112 examples, 0 failures`
|
|
174
|
+
|
|
175
|
+
**Step 4: Commit**
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
git add lib/localio/writers/twine_writer.rb spec/localio/writers/twine_writer_spec.rb
|
|
179
|
+
git commit -m "feat: add TwineWriter for Twine-compatible strings.txt output"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Task 3: Register :twine in LocalizableWriter
|
|
185
|
+
|
|
186
|
+
**Files:**
|
|
187
|
+
- Modify: `lib/localio/localizable_writer.rb`
|
|
188
|
+
|
|
189
|
+
**Step 1: Add the require and case branch**
|
|
190
|
+
|
|
191
|
+
At the top of `lib/localio/localizable_writer.rb`, add after the last `require` line:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
require 'localio/writers/twine_writer'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
In the `case platform` block, add before the `else`:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
when :twine
|
|
201
|
+
TwineWriter.write languages, terms, path, formatter, options
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Also update the error message in the `else` branch to include `:twine`:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
raise ArgumentError, 'Platform not supported! Current possibilities are :android, :ios, :json, :rails, :java_properties, :resx, :twine'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Step 2: Run the full suite to confirm nothing broke**
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
bundle exec rspec --format progress 2>&1 | tail -5
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Expected: `112 examples, 0 failures`
|
|
217
|
+
|
|
218
|
+
**Step 3: Commit**
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git add lib/localio/localizable_writer.rb
|
|
222
|
+
git commit -m "feat: register :twine platform in LocalizableWriter"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Task 4: Update README
|
|
228
|
+
|
|
229
|
+
**Files:**
|
|
230
|
+
- Modify: `README.md`
|
|
231
|
+
|
|
232
|
+
**Step 1: Add :twine to the supported platforms list**
|
|
233
|
+
|
|
234
|
+
Find the `#### Supported platforms` section and add after the `:resx` bullet:
|
|
235
|
+
|
|
236
|
+
```markdown
|
|
237
|
+
* `:twine` for [Twine](https://github.com/scelis/twine)-compatible `strings.txt` files containing all languages in a single file. The `output_path` is the directory where the file will be written.
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Step 2: Add a Twine source section after the ResX platform parameters section**
|
|
241
|
+
|
|
242
|
+
Find the `#### Supported sources` section header and add a new platform parameters sub-section before it (after the ResX section):
|
|
243
|
+
|
|
244
|
+
```markdown
|
|
245
|
+
##### Twine - :twine
|
|
246
|
+
|
|
247
|
+
By default the output file is named `strings.txt`. Use `:output_file` to override:
|
|
248
|
+
|
|
249
|
+
````ruby
|
|
250
|
+
platform :twine, :output_file => 'MyApp.strings'
|
|
251
|
+
````
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Step 3: Run the full suite one final time**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
bundle exec rspec --format progress 2>&1 | tail -5
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Expected: `112 examples, 0 failures`
|
|
261
|
+
|
|
262
|
+
**Step 4: Commit**
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
git add README.md
|
|
266
|
+
git commit -m "docs: document :twine platform in README"
|
|
267
|
+
```
|
|
@@ -5,6 +5,7 @@ require 'localio/writers/json_writer'
|
|
|
5
5
|
require 'localio/writers/rails_writer'
|
|
6
6
|
require 'localio/writers/java_properties_writer'
|
|
7
7
|
require 'localio/writers/resx_writer'
|
|
8
|
+
require 'localio/writers/twine_writer'
|
|
8
9
|
|
|
9
10
|
module LocalizableWriter
|
|
10
11
|
def self.write(platform, languages, terms, path, formatter, options)
|
|
@@ -23,8 +24,10 @@ module LocalizableWriter
|
|
|
23
24
|
JavaPropertiesWriter.write languages, terms, path, formatter, options
|
|
24
25
|
when :resx
|
|
25
26
|
ResXWriter.write languages, terms, path, formatter, options
|
|
27
|
+
when :twine
|
|
28
|
+
TwineWriter.write languages, terms, path, formatter, options
|
|
26
29
|
else
|
|
27
|
-
raise ArgumentError, 'Platform not supported! Current possibilities are :android, :ios, :json, :rails, :java_properties, :resx'
|
|
30
|
+
raise ArgumentError, 'Platform not supported! Current possibilities are :android, :ios, :json, :rails, :java_properties, :resx, :twine'
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require 'google_drive'
|
|
2
2
|
require 'localio/term'
|
|
3
|
-
require 'localio/config_store'
|
|
4
3
|
|
|
5
4
|
class GoogleDriveProcessor
|
|
6
5
|
|
|
@@ -19,9 +18,11 @@ class GoogleDriveProcessor
|
|
|
19
18
|
client_id = options[:client_id]
|
|
20
19
|
client_secret = options[:client_secret]
|
|
21
20
|
|
|
22
|
-
# We need client_id / client_secret
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
# We need client_id / client_secret (unless a service-account key is supplied)
|
|
22
|
+
unless options[:client_token].is_a?(String) && File.file?(options[:client_token].to_s)
|
|
23
|
+
raise ArgumentError, ':client_id required for Google Drive. Check how to get it here: https://developers.google.com/drive/web/auth/web-server' if client_id.nil?
|
|
24
|
+
raise ArgumentError, ':client_secret required for Google Drive. Check how to get it here: https://developers.google.com/drive/web/auth/web-server' if client_secret.nil?
|
|
25
|
+
end
|
|
25
26
|
|
|
26
27
|
override_default = nil
|
|
27
28
|
override_default = platform_options[:override_default] unless platform_options.nil? or platform_options[:override_default].nil?
|
|
@@ -29,50 +30,23 @@ class GoogleDriveProcessor
|
|
|
29
30
|
# Log in and get spreadsheet
|
|
30
31
|
puts 'Logging in to Google Drive...'
|
|
31
32
|
begin
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"https://docs.google.com/feeds/" +
|
|
38
|
-
"https://www.googleapis.com/auth/drive " +
|
|
39
|
-
"https://spreadsheets.google.com/feeds/"
|
|
40
|
-
auth.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
|
|
41
|
-
|
|
42
|
-
config = ConfigStore.new
|
|
43
|
-
|
|
44
|
-
access_token = nil
|
|
45
|
-
|
|
46
|
-
if options.has_key?(:client_token)
|
|
47
|
-
puts 'Refreshing auth token...'
|
|
48
|
-
auth.refresh_token = options[:client_token]
|
|
49
|
-
auth.refresh!
|
|
50
|
-
access_token = auth.access_token
|
|
51
|
-
elsif config.has? :refresh_token
|
|
52
|
-
puts 'Refreshing auth token...'
|
|
53
|
-
auth.refresh_token = config.get :refresh_token
|
|
54
|
-
auth.refresh!
|
|
55
|
-
access_token = auth.access_token
|
|
33
|
+
session = nil
|
|
34
|
+
|
|
35
|
+
# Service-account key file (JSON) path supplied via :client_token
|
|
36
|
+
if options[:client_token].is_a?(String) && File.file?(options[:client_token].to_s)
|
|
37
|
+
session = GoogleDrive::Session.from_service_account_key(options[:client_token])
|
|
56
38
|
else
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
# OAuth2 config-file flow (from_config saves/loads the refresh token)
|
|
40
|
+
config_path = File.join(Dir.home, '.localio_gdrive_config.json')
|
|
41
|
+
session = GoogleDrive::Session.from_config(
|
|
42
|
+
config_path,
|
|
43
|
+
client_id: client_id,
|
|
44
|
+
client_secret: client_secret
|
|
45
|
+
)
|
|
62
46
|
end
|
|
63
|
-
|
|
64
|
-
if !options.has_key?(:client_token)
|
|
65
|
-
puts 'Store auth data...'
|
|
66
|
-
config.store :refresh_token, auth.refresh_token
|
|
67
|
-
config.store :access_token, auth.access_token
|
|
68
|
-
config.persist
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Creates a session
|
|
72
|
-
session = GoogleDrive.login_with_oauth(access_token)
|
|
73
47
|
rescue => e
|
|
74
48
|
puts "Error: #{e.inspect}"
|
|
75
|
-
raise 'Couldn\'t access Google Drive. Check your values for :client_id and :client_secret
|
|
49
|
+
raise 'Couldn\'t access Google Drive. Check your values for :client_id and :client_secret.'
|
|
76
50
|
end
|
|
77
51
|
puts 'Logged in!'
|
|
78
52
|
|
|
@@ -125,7 +99,7 @@ class GoogleDriveProcessor
|
|
|
125
99
|
unless platform_options[:avoid_lang_downcase]
|
|
126
100
|
default_language = default_language.downcase
|
|
127
101
|
lang = lang.downcase
|
|
128
|
-
|
|
102
|
+
end
|
|
129
103
|
|
|
130
104
|
unless col_text.to_s == ''
|
|
131
105
|
languages.store lang, column
|
|
@@ -6,11 +6,13 @@ class TemplateHandler
|
|
|
6
6
|
full_template_path = File.join(File.dirname(File.expand_path(__FILE__)), "templates/#{template_name}")
|
|
7
7
|
input_file = File.open(full_template_path, 'rb')
|
|
8
8
|
template = input_file.read
|
|
9
|
+
|
|
9
10
|
input_file.close
|
|
10
11
|
renderer = ERB.new(template)
|
|
11
12
|
output = renderer.result(segments.get_binding)
|
|
12
13
|
output_file = File.new(generated_file_name, 'w')
|
|
13
|
-
|
|
14
|
+
output_replace = output.gsub(",}", "}")
|
|
15
|
+
output_file.write(output_replace)
|
|
14
16
|
output_file.close
|
|
15
17
|
|
|
16
18
|
destination_path = File.join(target_directory, generated_file_name)
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
<!-- Localizable created with localio. DO NOT MODIFY. -->
|
|
2
2
|
<resources>
|
|
3
3
|
<%
|
|
4
|
+
node_keys =[]
|
|
4
5
|
@segments.each do |term|
|
|
5
6
|
if term.is_comment? %>
|
|
6
7
|
<!-- <%= term.translation %> -->
|
|
7
|
-
<% else
|
|
8
|
-
|
|
8
|
+
<% else
|
|
9
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
10
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
11
|
+
node_keys.pop if term.key == '[end-node]'
|
|
12
|
+
else
|
|
13
|
+
if node_keys.length() > 0
|
|
14
|
+
key_join = node_keys.join("_")+"_"+term.key
|
|
15
|
+
else
|
|
16
|
+
key_join = term.key
|
|
17
|
+
end
|
|
18
|
+
%> <string name="<%= key_join %>"><%= term.translation %></string>
|
|
19
|
+
<% end
|
|
20
|
+
end
|
|
9
21
|
end
|
|
10
22
|
%>
|
|
11
23
|
</resources>
|
|
@@ -6,6 +6,20 @@ GENERATED - DO NOT MODIFY - use localio instead.
|
|
|
6
6
|
Created by localio.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
<%
|
|
10
|
-
|
|
9
|
+
<%
|
|
10
|
+
node_keys = []
|
|
11
|
+
@segments.each do |term|
|
|
12
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
13
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
14
|
+
node_keys.pop if term.key == '[end-node]'
|
|
15
|
+
else
|
|
16
|
+
if node_keys.length() >0
|
|
17
|
+
key_join = node_keys.join("_").capitalize+"_"+term.key.downcase
|
|
18
|
+
else
|
|
19
|
+
key_join = term.key
|
|
20
|
+
end
|
|
21
|
+
%>
|
|
22
|
+
#define kLocale<%= key_join %> NSLocalizedString(@"_<%= key_join %>",nil)
|
|
23
|
+
<%
|
|
24
|
+
end
|
|
11
25
|
end %>
|
|
@@ -6,12 +6,27 @@ GENERATED - DO NOT MODIFY - use the localio gem instead.
|
|
|
6
6
|
Created by localio.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
<%
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<%
|
|
10
|
+
node_keys = []
|
|
11
|
+
@segments.each do |term|
|
|
12
|
+
if term.is_comment?
|
|
13
|
+
%>
|
|
14
|
+
// <%= term.translation %>
|
|
15
|
+
<%
|
|
16
|
+
else
|
|
17
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
18
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
19
|
+
node_keys.pop if term.key == '[end-node]'
|
|
20
|
+
else
|
|
21
|
+
if node_keys.length() > 0
|
|
22
|
+
key_join = node_keys.join("_").capitalize+"_"+term.key.downcase
|
|
23
|
+
else
|
|
24
|
+
key_join = term.key
|
|
25
|
+
end
|
|
12
26
|
%>
|
|
13
|
-
|
|
14
|
-
<%
|
|
27
|
+
"_<%= key_join %>" = "<%= term.translation %>";
|
|
28
|
+
<%
|
|
29
|
+
end
|
|
15
30
|
end
|
|
16
31
|
end
|
|
17
32
|
%>
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
# Localizable created with localio. DO NOT MODIFY.
|
|
2
2
|
#
|
|
3
3
|
# Language: <%= @language %>
|
|
4
|
-
<%
|
|
4
|
+
<%
|
|
5
|
+
node_keys = []
|
|
6
|
+
@segments.each do |term|
|
|
5
7
|
if term.is_comment? %>
|
|
6
8
|
# <%= term.translation %>
|
|
7
|
-
<%
|
|
9
|
+
<%
|
|
10
|
+
else
|
|
11
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
12
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
13
|
+
node_keys.pop if term.key == '[end-node]'
|
|
14
|
+
else
|
|
15
|
+
if node_keys.length() > 0
|
|
16
|
+
key_join = node_keys.join("_")+"_"+term.key
|
|
17
|
+
else
|
|
18
|
+
key_join = term.key
|
|
19
|
+
end
|
|
20
|
+
%><%= key_join %>=<%= term.translation %>
|
|
8
21
|
<% end
|
|
9
22
|
end
|
|
23
|
+
end
|
|
10
24
|
%>
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
"language": "<%= @language %>"
|
|
5
5
|
},
|
|
6
6
|
"translations": {
|
|
7
|
-
<% @segments.each do |term|
|
|
7
|
+
<% @segments.each.with_index do |term, index|
|
|
8
8
|
term_value = term.translation
|
|
9
9
|
term_key = term.key
|
|
10
|
-
|
|
11
|
-
term_key = '
|
|
12
|
-
|
|
13
|
-
<%
|
|
10
|
+
count_to_string = index.to_s
|
|
11
|
+
term_key = '___comment_'+count_to_string+'___' if term.is_comment?
|
|
12
|
+
if term.key == '[init-node]'%>
|
|
13
|
+
"<%= term_value %>": {<% elsif term.key == '[end-node]'%>}<% unless term == @segments.last %>,<% end %><% else %>
|
|
14
|
+
"<%= term_key %>": "<%= term_value %>"<% unless term == @segments.last %>,<% end %><% end end %>
|
|
14
15
|
}
|
|
15
16
|
}
|
|
@@ -4,10 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
<%= @language %>:
|
|
6
6
|
<%
|
|
7
|
+
node_keys = []
|
|
7
8
|
@segments.each do |term|
|
|
8
|
-
|
|
9
|
+
if term.is_comment? %>
|
|
9
10
|
# <%= term.translation %>
|
|
10
|
-
<% else
|
|
11
|
-
|
|
11
|
+
<% else
|
|
12
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
13
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
14
|
+
node_keys.pop if term.key == '[end-node]'
|
|
15
|
+
else
|
|
16
|
+
if node_keys.length() > 0
|
|
17
|
+
key_join = node_keys.join("_")+"_"+term.key
|
|
18
|
+
else
|
|
19
|
+
key_join = term.key
|
|
20
|
+
end
|
|
21
|
+
%> <%= key_join %>: "<%= term.translation %>"
|
|
22
|
+
<% end
|
|
12
23
|
end
|
|
24
|
+
end
|
|
13
25
|
%>
|
|
@@ -122,14 +122,26 @@
|
|
|
122
122
|
<comment>Controls the Language and ensures that the font for all elements in the RootFrame aligns with the app's language. Set to the language code of this resource file's language.</comment>
|
|
123
123
|
</data>
|
|
124
124
|
<%
|
|
125
|
+
node_keys = []
|
|
125
126
|
@segments.each do |term|
|
|
126
127
|
if term.is_comment? %>
|
|
127
128
|
<!-- <%= term.translation %> -->
|
|
128
|
-
<% else
|
|
129
|
+
<% else
|
|
130
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
131
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
132
|
+
node_keys.pop if term.key == '[end-node]'
|
|
133
|
+
else
|
|
134
|
+
if node_keys.length() > 0
|
|
135
|
+
key_join = node_keys.join().capitalize+term.key.capitalize
|
|
136
|
+
else
|
|
137
|
+
key_join = term.key
|
|
138
|
+
end
|
|
139
|
+
%> <data name="<%= key_join %>" xml:space="preserve">
|
|
129
140
|
<value><![CDATA[<%= term.translation %>]]></value>
|
|
130
141
|
<comment/>
|
|
131
142
|
</data>
|
|
132
|
-
<%
|
|
143
|
+
<% end
|
|
144
|
+
end
|
|
133
145
|
end
|
|
134
146
|
%>
|
|
135
147
|
|