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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab5c7db7901943df5125445d1151851ba8831d9407dca76a43f08f7127499639
4
- data.tar.gz: a2ad9aab66aea7dcea8f41b0c7653d7cde6b8e78b3bdb0390fe1e8ae474f67c2
3
+ metadata.gz: e811c8aa9cf8dabaff74b28dca5476b389c46e27a2dbb5addd87177f28abf566
4
+ data.tar.gz: 6536d23be9e050ec9d579e6fd0bd35af573eba46536f2d711acaf5d8ee086dc3
5
5
  SHA512:
6
- metadata.gz: 04d467b401399b5a7810bdbd2fa1d95cb820c0333942ee36a68bd5ec298596229649d4a665c2c35deae8e9fff5ebead2aa941433960b3fb83410be6d9297a4cd
7
- data.tar.gz: 7e3a0348a4d4cc70f8aca67fd52e5bba3b0986df6622e7c000beb8fe6690a28c8bec9c500935868eda2d468263fdcedf1e5996b458cc37ddfc88528bea9d5cc7
6
+ metadata.gz: 2217300fc3ff1610320e23053d97969c93983caf57ecbbc064c5e4018ba4d599b90c71a70ee27bfa5e38ffc3f52bf8c539110c9c55eefdcec4fe8c0960a4d5bd
7
+ data.tar.gz: 40b1707ad311b028a76a4a0b5f26485153642fc48a89b2e488b7c7b3c13e146f82b55f72bf6a18481082a76b486719c33f1936c83a706d94b14afd7a28b3e230
data/.gitignore CHANGED
@@ -19,4 +19,5 @@ tmp
19
19
  .idea/
20
20
  testing/
21
21
  .vscode/launch.json
22
- .worktrees/
22
+ .worktrees/
23
+ vendor/
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- localio (0.1.8)
5
- csv (>= 3.2)
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.10.5)
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.0)
27
+ faraday-net_http_persistent (~> 1.1)
29
28
  faraday-patron (~> 1.0)
30
29
  faraday-rack (~> 1.0)
31
- faraday-retry (~> 1.0)
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
@@ -1,3 +1,3 @@
1
1
  module Localio
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -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.0
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-23 00:00:00.000000000 Z
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.5.22
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.