localio 0.2.0 → 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 +4 -4
- data/.gitignore +2 -1
- data/Gemfile.lock +7 -11
- data/README.md +9 -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/version.rb +1 -1
- data/lib/localio/writers/twine_writer.rb +48 -0
- data/spec/localio/writers/twine_writer_spec.rb +68 -0
- metadata +10 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e811c8aa9cf8dabaff74b28dca5476b389c46e27a2dbb5addd87177f28abf566
|
|
4
|
+
data.tar.gz: 6536d23be9e050ec9d579e6fd0bd35af573eba46536f2d711acaf5d8ee086dc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2217300fc3ff1610320e23053d97969c93983caf57ecbbc064c5e4018ba4d599b90c71a70ee27bfa5e38ffc3f52bf8c539110c9c55eefdcec4fe8c0960a4d5bd
|
|
7
|
+
data.tar.gz: 40b1707ad311b028a76a4a0b5f26485153642fc48a89b2e488b7c7b3c13e146f82b55f72bf6a18481082a76b486719c33f1936c83a706d94b14afd7a28b3e230
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
localio (0.
|
|
5
|
-
csv (
|
|
4
|
+
localio (0.2.0)
|
|
5
|
+
csv (~> 3.2)
|
|
6
6
|
google_drive (~> 3.0)
|
|
7
7
|
nokogiri (~> 1.16)
|
|
8
8
|
simple_xlsx_reader (~> 2.0)
|
|
@@ -18,29 +18,25 @@ GEM
|
|
|
18
18
|
csv (3.3.5)
|
|
19
19
|
declarative (0.0.20)
|
|
20
20
|
diff-lcs (1.6.2)
|
|
21
|
-
faraday (1.
|
|
21
|
+
faraday (1.8.0)
|
|
22
22
|
faraday-em_http (~> 1.0)
|
|
23
23
|
faraday-em_synchrony (~> 1.0)
|
|
24
24
|
faraday-excon (~> 1.1)
|
|
25
|
-
faraday-httpclient (~> 1.0)
|
|
26
|
-
faraday-multipart (~> 1.0)
|
|
25
|
+
faraday-httpclient (~> 1.0.1)
|
|
27
26
|
faraday-net_http (~> 1.0)
|
|
28
|
-
faraday-net_http_persistent (~> 1.
|
|
27
|
+
faraday-net_http_persistent (~> 1.1)
|
|
29
28
|
faraday-patron (~> 1.0)
|
|
30
29
|
faraday-rack (~> 1.0)
|
|
31
|
-
|
|
30
|
+
multipart-post (>= 1.2, < 3)
|
|
32
31
|
ruby2_keywords (>= 0.0.4)
|
|
33
32
|
faraday-em_http (1.0.0)
|
|
34
33
|
faraday-em_synchrony (1.0.1)
|
|
35
34
|
faraday-excon (1.1.0)
|
|
36
35
|
faraday-httpclient (1.0.1)
|
|
37
|
-
faraday-multipart (1.2.0)
|
|
38
|
-
multipart-post (~> 2.0)
|
|
39
36
|
faraday-net_http (1.0.2)
|
|
40
37
|
faraday-net_http_persistent (1.2.0)
|
|
41
38
|
faraday-patron (1.0.0)
|
|
42
39
|
faraday-rack (1.0.0)
|
|
43
|
-
faraday-retry (1.0.3)
|
|
44
40
|
google-apis-core (0.11.3)
|
|
45
41
|
addressable (~> 2.5, >= 2.5.1)
|
|
46
42
|
googleauth (>= 0.16.2, < 2.a)
|
|
@@ -131,7 +127,7 @@ PLATFORMS
|
|
|
131
127
|
|
|
132
128
|
DEPENDENCIES
|
|
133
129
|
localio!
|
|
134
|
-
rake
|
|
130
|
+
rake (~> 13.0)
|
|
135
131
|
rspec (~> 3.0)
|
|
136
132
|
|
|
137
133
|
BUNDLED WITH
|
data/README.md
CHANGED
|
@@ -73,6 +73,7 @@ Option | Description
|
|
|
73
73
|
* `:json` for an easy JSON format for localizables. The `output_path` is yours to decide :)
|
|
74
74
|
* `:java_properties` for .properties files used mainly in Java. Files named language_(lang).properties will be generated in `output_path`'s root directory.
|
|
75
75
|
* `:resx` for .resx files used by .NET projects, e.g. Windows Forms, Windows Phone or Xamarin.
|
|
76
|
+
* `: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.
|
|
76
77
|
|
|
77
78
|
#### Extra platform parameters
|
|
78
79
|
|
|
@@ -103,6 +104,14 @@ platform :resx, :resource_file => "WebResources"
|
|
|
103
104
|
# ... rest of your Locfile ...
|
|
104
105
|
````
|
|
105
106
|
|
|
107
|
+
##### Twine - :twine
|
|
108
|
+
|
|
109
|
+
By default the output file is named `strings.txt`. Use `:output_file` to override:
|
|
110
|
+
|
|
111
|
+
````ruby
|
|
112
|
+
platform :twine, :output_file => 'MyApp.strings'
|
|
113
|
+
````
|
|
114
|
+
|
|
106
115
|
#### Supported sources
|
|
107
116
|
|
|
108
117
|
##### Google Drive
|
|
@@ -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
|
data/lib/localio/version.rb
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'localio/formatter'
|
|
3
|
+
|
|
4
|
+
class TwineWriter
|
|
5
|
+
def self.write(languages, terms, path, formatter, options)
|
|
6
|
+
puts 'Writing Twine translations...'
|
|
7
|
+
|
|
8
|
+
default_language = options[:default_language]
|
|
9
|
+
output_filename = options[:output_file] || 'strings.txt'
|
|
10
|
+
|
|
11
|
+
FileUtils.mkdir_p(path)
|
|
12
|
+
|
|
13
|
+
File.open(File.join(path, output_filename), 'w') do |f|
|
|
14
|
+
pending_comment = nil
|
|
15
|
+
|
|
16
|
+
terms.each do |term|
|
|
17
|
+
if term.is_comment?
|
|
18
|
+
pending_comment = term.values[default_language]
|
|
19
|
+
elsif term.keyword == '[init-node]'
|
|
20
|
+
f.puts "[[#{term.values[default_language]}]]"
|
|
21
|
+
pending_comment = nil
|
|
22
|
+
elsif term.keyword == '[end-node]'
|
|
23
|
+
f.puts ''
|
|
24
|
+
pending_comment = nil
|
|
25
|
+
else
|
|
26
|
+
key = Formatter.format(term.keyword, formatter, method(:twine_key_formatter))
|
|
27
|
+
f.puts "\t[#{key}]"
|
|
28
|
+
languages.keys.each do |lang|
|
|
29
|
+
f.puts "\t\t#{lang} = #{term.values[lang]}"
|
|
30
|
+
end
|
|
31
|
+
if pending_comment
|
|
32
|
+
f.puts "\t\tcomment = #{pending_comment}"
|
|
33
|
+
pending_comment = nil
|
|
34
|
+
end
|
|
35
|
+
f.puts ''
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
puts " > #{output_filename.yellow}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def self.twine_key_formatter(key)
|
|
46
|
+
key.space_to_underscore.strip_tag.downcase
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'localio/string_helper'
|
|
2
|
+
require 'localio/term'
|
|
3
|
+
require 'localio/formatter'
|
|
4
|
+
require 'localio/writers/twine_writer'
|
|
5
|
+
|
|
6
|
+
RSpec.describe TwineWriter do
|
|
7
|
+
include_context 'standard terms'
|
|
8
|
+
# standard terms provides: languages {'en'=>1,'es'=>2,'fr'=>3},
|
|
9
|
+
# default_language 'en', and terms:
|
|
10
|
+
# [comment] "Section General", app_name, greeting, dots_test, ampersand_test
|
|
11
|
+
|
|
12
|
+
let(:options) { { default_language: 'en' } }
|
|
13
|
+
|
|
14
|
+
describe '.write' do
|
|
15
|
+
it 'creates strings.txt in the output path' do
|
|
16
|
+
Dir.mktmpdir do |tmpdir|
|
|
17
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, terms, tmpdir, :smart, options) }
|
|
18
|
+
expect(File).to exist(File.join(tmpdir, 'strings.txt'))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'uses a custom filename when :output_file is specified' do
|
|
23
|
+
Dir.mktmpdir do |tmpdir|
|
|
24
|
+
Dir.chdir(tmpdir) do
|
|
25
|
+
TwineWriter.write(languages, terms, tmpdir, :smart, options.merge(output_file: 'translations.txt'))
|
|
26
|
+
end
|
|
27
|
+
expect(File).to exist(File.join(tmpdir, 'translations.txt'))
|
|
28
|
+
expect(File).not_to exist(File.join(tmpdir, 'strings.txt'))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'includes all languages for each key' do
|
|
33
|
+
Dir.mktmpdir do |tmpdir|
|
|
34
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, terms, tmpdir, :smart, options) }
|
|
35
|
+
content = File.read(File.join(tmpdir, 'strings.txt'))
|
|
36
|
+
expect(content).to include('en = My App')
|
|
37
|
+
expect(content).to include('es = Mi Aplicación')
|
|
38
|
+
expect(content).to include('fr = Mon Application')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'writes [init-node] terms as [[section]] headers' do
|
|
43
|
+
section_terms = [
|
|
44
|
+
Term.new('[init-node]').tap { |t| t.values['en'] = 'General'; t.values['es'] = 'General'; t.values['fr'] = 'General' },
|
|
45
|
+
Term.new('app_name').tap { |t| t.values['en'] = 'My App'; t.values['es'] = 'Mi App'; t.values['fr'] = 'Mon App' },
|
|
46
|
+
Term.new('[end-node]').tap { |t| t.values['en'] = 'end'; t.values['es'] = 'end'; t.values['fr'] = 'end' },
|
|
47
|
+
]
|
|
48
|
+
Dir.mktmpdir do |tmpdir|
|
|
49
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, section_terms, tmpdir, :smart, options) }
|
|
50
|
+
content = File.read(File.join(tmpdir, 'strings.txt'))
|
|
51
|
+
expect(content).to include('[[General]]')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'attaches [comment] value as comment = on the following key' do
|
|
56
|
+
Dir.mktmpdir do |tmpdir|
|
|
57
|
+
Dir.chdir(tmpdir) { TwineWriter.write(languages, terms, tmpdir, :smart, options) }
|
|
58
|
+
content = File.read(File.join(tmpdir, 'strings.txt'))
|
|
59
|
+
# [comment] "Section General" appears before app_name and attaches to it
|
|
60
|
+
expect(content).to include("\t\tcomment = Section General")
|
|
61
|
+
# The comment block appears before the greeting key block
|
|
62
|
+
comment_pos = content.index('comment = Section General')
|
|
63
|
+
greeting_pos = content.index('[greeting]')
|
|
64
|
+
expect(comment_pos).to be < greeting_pos
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: localio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nacho Lopez
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -129,6 +129,8 @@ files:
|
|
|
129
129
|
- bin/localize
|
|
130
130
|
- docs/plans/2026-02-23-modernization-design.md
|
|
131
131
|
- docs/plans/2026-02-23-modernization.md
|
|
132
|
+
- docs/plans/2026-02-23-twine-writer-design.md
|
|
133
|
+
- docs/plans/2026-02-23-twine-writer.md
|
|
132
134
|
- lib/localio.rb
|
|
133
135
|
- lib/localio/config_store.rb
|
|
134
136
|
- lib/localio/filter.rb
|
|
@@ -162,6 +164,7 @@ files:
|
|
|
162
164
|
- lib/localio/writers/rails_writer.rb
|
|
163
165
|
- lib/localio/writers/resx_writer.rb
|
|
164
166
|
- lib/localio/writers/swift_writer.rb
|
|
167
|
+
- lib/localio/writers/twine_writer.rb
|
|
165
168
|
- localio.gemspec
|
|
166
169
|
- spec/fixtures/sample.csv
|
|
167
170
|
- spec/localio/filter_spec.rb
|
|
@@ -182,6 +185,7 @@ files:
|
|
|
182
185
|
- spec/localio/writers/rails_writer_spec.rb
|
|
183
186
|
- spec/localio/writers/resx_writer_spec.rb
|
|
184
187
|
- spec/localio/writers/swift_writer_spec.rb
|
|
188
|
+
- spec/localio/writers/twine_writer_spec.rb
|
|
185
189
|
- spec/localio_spec.rb
|
|
186
190
|
- spec/spec_helper.rb
|
|
187
191
|
- spec/support/shared_terms.rb
|
|
@@ -189,7 +193,7 @@ homepage: https://github.com/mrmans0n/localio
|
|
|
189
193
|
licenses:
|
|
190
194
|
- MIT
|
|
191
195
|
metadata: {}
|
|
192
|
-
post_install_message:
|
|
196
|
+
post_install_message:
|
|
193
197
|
rdoc_options: []
|
|
194
198
|
require_paths:
|
|
195
199
|
- lib
|
|
@@ -204,8 +208,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
204
208
|
- !ruby/object:Gem::Version
|
|
205
209
|
version: '0'
|
|
206
210
|
requirements: []
|
|
207
|
-
rubygems_version: 3.
|
|
208
|
-
signing_key:
|
|
211
|
+
rubygems_version: 3.0.3.1
|
|
212
|
+
signing_key:
|
|
209
213
|
specification_version: 4
|
|
210
214
|
summary: Generates Android, iOS, Rails, JSON, Java Properties, and .NET ResX localization
|
|
211
215
|
files from spreadsheet sources.
|