localio 0.1.7 → 0.2.0
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 +3 -1
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/Gemfile.lock +138 -0
- data/README.md +27 -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/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/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_spec.rb +62 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/shared_terms.rb +35 -0
- metadata +60 -49
|
@@ -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
|
|
|
@@ -8,6 +8,19 @@ Created by localio
|
|
|
8
8
|
|
|
9
9
|
import Foundation
|
|
10
10
|
|
|
11
|
-
<%
|
|
12
|
-
|
|
11
|
+
<%
|
|
12
|
+
node_keys = []
|
|
13
|
+
@segments.each do |term|
|
|
14
|
+
if term.key == '[init-node]' or term.key == '[end-node]'
|
|
15
|
+
node_keys << term.translation if term.key == '[init-node]'
|
|
16
|
+
node_keys.pop if term.key == '[end-node]'
|
|
17
|
+
else
|
|
18
|
+
if node_keys.length() >0
|
|
19
|
+
key_join = node_keys.join("_").capitalize+"_"+term.key.downcase
|
|
20
|
+
else
|
|
21
|
+
key_join = term.key
|
|
22
|
+
end
|
|
23
|
+
%>
|
|
24
|
+
let kLocale<%= key_join %>: String = { return NSLocalizedString("_<%= key_join %>", comment: "") }()<%
|
|
25
|
+
end
|
|
13
26
|
end %>
|
data/lib/localio/version.rb
CHANGED
|
@@ -25,7 +25,7 @@ class IosWriter
|
|
|
25
25
|
|
|
26
26
|
unless term.is_comment?
|
|
27
27
|
constant_key = ios_constant_formatter term.keyword
|
|
28
|
-
constant_value =
|
|
28
|
+
constant_value = translation
|
|
29
29
|
constant_segment = Segment.new(constant_key, constant_value, lang)
|
|
30
30
|
constant_segments.segments << constant_segment
|
|
31
31
|
end
|
|
@@ -45,11 +45,11 @@ class IosWriter
|
|
|
45
45
|
private
|
|
46
46
|
|
|
47
47
|
def self.ios_key_formatter(key)
|
|
48
|
-
|
|
48
|
+
key.space_to_underscore.strip_tag.capitalize
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def self.ios_constant_formatter(key)
|
|
52
|
-
|
|
52
|
+
key.space_to_underscore.strip_tag.camel_case
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
end
|
|
@@ -25,7 +25,7 @@ class SwiftWriter
|
|
|
25
25
|
|
|
26
26
|
unless term.is_comment?
|
|
27
27
|
constant_key = swift_constant_formatter term.keyword
|
|
28
|
-
constant_value =
|
|
28
|
+
constant_value = translation
|
|
29
29
|
constant_segment = Segment.new(constant_key, constant_value, lang)
|
|
30
30
|
constant_segments.segments << constant_segment
|
|
31
31
|
end
|
|
@@ -44,10 +44,10 @@ class SwiftWriter
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
def self.swift_key_formatter(key)
|
|
47
|
-
|
|
47
|
+
key.space_to_underscore.strip_tag.capitalize
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def self.swift_constant_formatter(key)
|
|
51
|
-
|
|
51
|
+
key.space_to_underscore.strip_tag.camel_case
|
|
52
52
|
end
|
|
53
53
|
end
|
data/localio.gemspec
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
3
|
require 'localio/version'
|
|
5
4
|
|
|
6
5
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name
|
|
8
|
-
spec.version
|
|
9
|
-
spec.authors
|
|
10
|
-
spec.email
|
|
11
|
-
spec.description
|
|
12
|
-
spec.summary
|
|
13
|
-
spec.homepage
|
|
14
|
-
spec.license
|
|
6
|
+
spec.name = "localio"
|
|
7
|
+
spec.version = Localio::VERSION
|
|
8
|
+
spec.authors = ["Nacho Lopez"]
|
|
9
|
+
spec.email = ["nacho@nlopez.io"]
|
|
10
|
+
spec.description = %q{Automatic Localizable file generation for multiple platforms}
|
|
11
|
+
spec.summary = %q{Generates Android, iOS, Rails, JSON, Java Properties, and .NET ResX localization files from spreadsheet sources.}
|
|
12
|
+
spec.homepage = "https://github.com/mrmans0n/localio"
|
|
13
|
+
spec.license = "MIT"
|
|
15
14
|
|
|
16
|
-
spec.files
|
|
17
|
-
spec.executables
|
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
15
|
+
spec.files = `git ls-files`.split("\n")
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
19
17
|
spec.require_paths = ["lib"]
|
|
20
18
|
|
|
21
|
-
spec.
|
|
19
|
+
spec.required_ruby_version = ">= 3.2"
|
|
22
20
|
|
|
23
|
-
spec.add_development_dependency "rspec"
|
|
21
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
22
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
24
23
|
|
|
25
|
-
spec.
|
|
26
|
-
|
|
27
|
-
spec.
|
|
28
|
-
spec.
|
|
29
|
-
|
|
30
|
-
spec.add_dependency "micro-optparse", "~> 1.2"
|
|
31
|
-
spec.add_dependency "google_drive", "~> 1.0"
|
|
32
|
-
spec.add_dependency "spreadsheet", "~> 1.0"
|
|
33
|
-
spec.add_dependency "simple_xlsx_reader", "~> 1.0"
|
|
34
|
-
spec.add_dependency "nokogiri", "~> 1.6"
|
|
24
|
+
spec.add_dependency "google_drive", "~> 3.0"
|
|
25
|
+
spec.add_dependency "spreadsheet", "~> 1.3"
|
|
26
|
+
spec.add_dependency "simple_xlsx_reader", "~> 2.0"
|
|
27
|
+
spec.add_dependency "nokogiri", "~> 1.16"
|
|
28
|
+
spec.add_dependency "csv", "~> 3.2"
|
|
35
29
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Title Row,,,
|
|
2
|
+
[key],*en,es,fr
|
|
3
|
+
[comment],Section General,Section General,Section General
|
|
4
|
+
app_name,My App,Mi Aplicación,Mon Application
|
|
5
|
+
greeting,Hello %@ world,Hola %@ mundo,Bonjour %@ monde
|
|
6
|
+
dots_test,Wait...,Espera...,Attendez...
|
|
7
|
+
ampersand_test,Tom & Jerry,Tom & Jerry,Tom & Jerry
|
|
8
|
+
[init-node],module,module,module
|
|
9
|
+
nested_key,Module Key,Clave Módulo,Clé Module
|
|
10
|
+
[end-node],end,end,end
|
|
11
|
+
[end],,,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'localio/term'
|
|
2
|
+
require 'localio/filter'
|
|
3
|
+
|
|
4
|
+
RSpec.describe Filter do
|
|
5
|
+
let(:terms) do
|
|
6
|
+
['app_name', 'app_title', 'settings_title', 'settings_back', '[comment]'].map { |kw| Term.new(kw) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe '.apply_filter' do
|
|
10
|
+
it 'returns all terms when no filters set' do
|
|
11
|
+
expect(Filter.apply_filter(terms, nil, nil)).to eq(terms)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context 'with only filter' do
|
|
15
|
+
it 'keeps terms matching the regex' do
|
|
16
|
+
result = Filter.apply_filter(terms, { keys: 'app_' }, nil)
|
|
17
|
+
expect(result.map(&:keyword)).to contain_exactly('app_name', 'app_title')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'returns empty array when nothing matches' do
|
|
21
|
+
expect(Filter.apply_filter(terms, { keys: 'nonexistent' }, nil)).to be_empty
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'with except filter' do
|
|
26
|
+
it 'excludes terms matching the regex' do
|
|
27
|
+
result = Filter.apply_filter(terms, nil, { keys: 'settings_' })
|
|
28
|
+
expect(result.map(&:keyword)).not_to include('settings_title', 'settings_back')
|
|
29
|
+
expect(result.map(&:keyword)).to include('app_name', 'app_title')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with both filters' do
|
|
34
|
+
it 'applies only first then except' do
|
|
35
|
+
result = Filter.apply_filter(terms, { keys: 'app_' }, { keys: 'title' })
|
|
36
|
+
expect(result.map(&:keyword)).to contain_exactly('app_name')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'localio/string_helper'
|
|
2
|
+
require 'localio/formatter'
|
|
3
|
+
|
|
4
|
+
RSpec.describe Formatter do
|
|
5
|
+
let(:smart_callback) { ->(key) { key.upcase } }
|
|
6
|
+
|
|
7
|
+
describe '.format' do
|
|
8
|
+
it ':smart delegates to callback' do
|
|
9
|
+
expect(Formatter.format('hello', :smart, smart_callback)).to eq('HELLO')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it ':none returns key unchanged' do
|
|
13
|
+
expect(Formatter.format('Hello World', :none, smart_callback)).to eq('Hello World')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it ':camel_case converts to CamelCase' do
|
|
17
|
+
expect(Formatter.format('hello world', :camel_case, smart_callback)).to eq('HelloWorld')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it ':camel_case strips single-letter bracket tags' do
|
|
21
|
+
expect(Formatter.format('[a]hello', :camel_case, smart_callback)).to eq('Hello')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it ':snake_case converts spaces to underscores and downcases' do
|
|
25
|
+
expect(Formatter.format('Hello World', :snake_case, smart_callback)).to eq('hello_world')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'raises ArgumentError for unknown formatter' do
|
|
29
|
+
expect { Formatter.format('key', :unknown, smart_callback) }.to raise_error(ArgumentError)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'localio/term'
|
|
2
|
+
require 'localio/string_helper'
|
|
3
|
+
require 'localio/processors/csv_processor'
|
|
4
|
+
|
|
5
|
+
RSpec.describe CsvProcessor do
|
|
6
|
+
let(:fixture_path) { File.expand_path('../../../fixtures/sample.csv', __FILE__) }
|
|
7
|
+
let(:platform_options) { {} }
|
|
8
|
+
let(:options) { { path: fixture_path } }
|
|
9
|
+
|
|
10
|
+
describe '.load_localizables' do
|
|
11
|
+
subject(:result) { CsvProcessor.load_localizables(platform_options, options) }
|
|
12
|
+
|
|
13
|
+
it 'returns languages en, es, fr' do
|
|
14
|
+
expect(result[:languages].keys).to contain_exactly('en', 'es', 'fr')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'sets en as default language (marked with *)' do
|
|
18
|
+
expect(result[:default_language]).to eq('en')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns 8 terms between [key] and [end]' do
|
|
22
|
+
expect(result[:segments].count).to eq(8)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'parses term keywords correctly' do
|
|
26
|
+
keywords = result[:segments].map(&:keyword)
|
|
27
|
+
expect(keywords).to include('app_name', 'greeting', '[comment]', '[init-node]', '[end-node]')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'parses translations for each language' do
|
|
31
|
+
app_name = result[:segments].find { |t| t.keyword == 'app_name' }
|
|
32
|
+
expect(app_name.values['en']).to eq('My App')
|
|
33
|
+
expect(app_name.values['es']).to eq('Mi Aplicación')
|
|
34
|
+
expect(app_name.values['fr']).to eq('Mon Application')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'identifies comment rows' do
|
|
38
|
+
comment = result[:segments].find { |t| t.keyword == '[comment]' }
|
|
39
|
+
expect(comment.is_comment?).to be true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'raises ArgumentError when :path is missing' do
|
|
43
|
+
expect { CsvProcessor.load_localizables({}, {}) }
|
|
44
|
+
.to raise_error(ArgumentError, /:path attribute is missing/)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'raises IndexError when [key] marker is missing' do
|
|
48
|
+
Dir.mktmpdir do |tmpdir|
|
|
49
|
+
path = File.join(tmpdir, 'bad.csv')
|
|
50
|
+
File.write(path, "no,key,row\ndata,here,\n[end],,,\n")
|
|
51
|
+
expect { CsvProcessor.load_localizables({}, { path: path }) }
|
|
52
|
+
.to raise_error(IndexError, /Could not find any \[key\]/)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'raises IndexError when [end] marker is missing' do
|
|
57
|
+
Dir.mktmpdir do |tmpdir|
|
|
58
|
+
path = File.join(tmpdir, 'bad.csv')
|
|
59
|
+
File.write(path, "title,,,\n[key],*en,es,\ndata,val,val,\n")
|
|
60
|
+
expect { CsvProcessor.load_localizables({}, { path: path }) }
|
|
61
|
+
.to raise_error(IndexError, /Could not find any \[end\]/)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context 'with override_default option' do
|
|
66
|
+
let(:platform_options) { { override_default: 'es' } }
|
|
67
|
+
|
|
68
|
+
it 'uses the overridden default language' do
|
|
69
|
+
expect(result[:default_language]).to eq('es')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'with avoid_lang_downcase option' do
|
|
74
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
75
|
+
let(:options) do
|
|
76
|
+
path = File.join(tmpdir, 'upper.csv')
|
|
77
|
+
File.write(path, "Title,,,\n[key],*EN,ES,FR\napp_name,My App,Mi App,Mon App\n[end],,,\n")
|
|
78
|
+
{ path: path }
|
|
79
|
+
end
|
|
80
|
+
let(:platform_options) { { avoid_lang_downcase: true } }
|
|
81
|
+
|
|
82
|
+
after { FileUtils.rm_rf(tmpdir) }
|
|
83
|
+
|
|
84
|
+
it 'preserves language case' do
|
|
85
|
+
expect(result[:languages].keys).to contain_exactly('EN', 'ES', 'FR')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|