localio 0.2.2 → 0.2.4

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.
@@ -1,72 +0,0 @@
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
@@ -1,267 +0,0 @@
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
- ```