localio 0.1.6 → 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 +34 -35
- 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/processor.rb +2 -1
- data/lib/localio/processors/csv_processor.rb +12 -3
- data/lib/localio/processors/google_drive_processor.rb +36 -48
- data/lib/localio/processors/xls_processor.rb +12 -3
- data/lib/localio/processors/xlsx_processor.rb +12 -3
- 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
data/lib/localio/processor.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'localio/processors/csv_processor'
|
|
|
5
5
|
|
|
6
6
|
module Processor
|
|
7
7
|
def self.load_localizables(platform_options, service, options)
|
|
8
|
+
puts "Service: #{service}"
|
|
8
9
|
case service
|
|
9
10
|
when :google_drive
|
|
10
11
|
GoogleDriveProcessor.load_localizables platform_options, options
|
|
@@ -18,4 +19,4 @@ module Processor
|
|
|
18
19
|
raise ArgumentError, 'Unsupported service! Try with :google_drive, :csv, :xlsx or :xls in the source argument'
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
|
-
end
|
|
22
|
+
end
|
|
@@ -36,8 +36,17 @@ class CsvProcessor
|
|
|
36
36
|
for column in 1..csv_file[first_valid_row_index].count-1
|
|
37
37
|
col_all = csv_file[first_valid_row_index][column].to_s
|
|
38
38
|
col_all.each_line(' ') do |col_text|
|
|
39
|
-
default_language = col_text.
|
|
40
|
-
|
|
39
|
+
default_language = col_text.gsub('*', '') if col_text.include? '*'
|
|
40
|
+
lang = col_text.gsub('*', '')
|
|
41
|
+
|
|
42
|
+
unless platform_options[:avoid_lang_downcase]
|
|
43
|
+
default_language = default_language.downcase
|
|
44
|
+
lang = lang.downcase
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
unless col_text.to_s == ''
|
|
48
|
+
languages.store lang, column
|
|
49
|
+
end
|
|
41
50
|
end
|
|
42
51
|
end
|
|
43
52
|
|
|
@@ -78,4 +87,4 @@ class CsvProcessor
|
|
|
78
87
|
|
|
79
88
|
end
|
|
80
89
|
|
|
81
|
-
end
|
|
90
|
+
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
|
|
|
@@ -90,9 +64,14 @@ class GoogleDriveProcessor
|
|
|
90
64
|
abort "More than one match found (#{matching_spreadsheets.join ', '}). You have to be more specific!"
|
|
91
65
|
end
|
|
92
66
|
|
|
67
|
+
sheet = options[:sheet]
|
|
68
|
+
worksheet = if sheet.is_a? Integer
|
|
69
|
+
matching_spreadsheets[0].worksheets[sheet]
|
|
70
|
+
elsif sheet.is_a? String
|
|
71
|
+
matching_spreadsheets[0].worksheets.detect { |s| s.title == sheet }
|
|
72
|
+
end
|
|
73
|
+
|
|
93
74
|
|
|
94
|
-
# TODO we could pass a :page_index in the options hash and get that worksheet instead, defaulting to zero?
|
|
95
|
-
worksheet = matching_spreadsheets[0].worksheets[0]
|
|
96
75
|
raise 'Unable to retrieve the first worksheet from the spreadsheet. Are there any pages?' if worksheet.nil?
|
|
97
76
|
|
|
98
77
|
# At this point we have the worksheet, so we want to store all the key / values
|
|
@@ -114,8 +93,17 @@ class GoogleDriveProcessor
|
|
|
114
93
|
for column in 2..worksheet.max_cols
|
|
115
94
|
col_all = worksheet[first_valid_row_index, column]
|
|
116
95
|
col_all.each_line(' ') do |col_text|
|
|
117
|
-
default_language = col_text.
|
|
118
|
-
|
|
96
|
+
default_language = col_text.gsub('*', '') if col_text.include? '*'
|
|
97
|
+
lang = col_text.gsub('*', '')
|
|
98
|
+
|
|
99
|
+
unless platform_options[:avoid_lang_downcase]
|
|
100
|
+
default_language = default_language.downcase
|
|
101
|
+
lang = lang.downcase
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
unless col_text.to_s == ''
|
|
105
|
+
languages.store lang, column
|
|
106
|
+
end
|
|
119
107
|
end
|
|
120
108
|
end
|
|
121
109
|
|
|
@@ -40,8 +40,17 @@ class XlsProcessor
|
|
|
40
40
|
for column in 1..worksheet.column_count
|
|
41
41
|
col_all = worksheet[first_valid_row_index, column].to_s
|
|
42
42
|
col_all.each_line(' ') do |col_text|
|
|
43
|
-
default_language = col_text.
|
|
44
|
-
|
|
43
|
+
default_language = col_text.gsub('*', '') if col_text.include? '*'
|
|
44
|
+
lang = col_text.gsub('*', '')
|
|
45
|
+
|
|
46
|
+
unless platform_options[:avoid_lang_downcase]
|
|
47
|
+
default_language = default_language.downcase
|
|
48
|
+
lang = lang.downcase
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless col_text.to_s == ''
|
|
52
|
+
languages.store lang, column
|
|
53
|
+
end
|
|
45
54
|
end
|
|
46
55
|
end
|
|
47
56
|
|
|
@@ -82,4 +91,4 @@ class XlsProcessor
|
|
|
82
91
|
|
|
83
92
|
end
|
|
84
93
|
|
|
85
|
-
end
|
|
94
|
+
end
|
|
@@ -43,8 +43,17 @@ class XlsxProcessor
|
|
|
43
43
|
for column in 1..worksheet.rows[first_valid_row_index].count-1
|
|
44
44
|
col_all = worksheet.rows[first_valid_row_index][column].to_s
|
|
45
45
|
col_all.each_line(' ') do |col_text|
|
|
46
|
-
default_language = col_text.
|
|
47
|
-
|
|
46
|
+
default_language = col_text.gsub('*', '') if col_text.include? '*'
|
|
47
|
+
lang = col_text.gsub('*', '')
|
|
48
|
+
|
|
49
|
+
unless platform_options[:avoid_lang_downcase]
|
|
50
|
+
default_language = default_language.downcase
|
|
51
|
+
lang = lang.downcase
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
unless col_text.to_s == ''
|
|
55
|
+
languages.store lang, column
|
|
56
|
+
end
|
|
48
57
|
end
|
|
49
58
|
end
|
|
50
59
|
|
|
@@ -85,4 +94,4 @@ class XlsxProcessor
|
|
|
85
94
|
|
|
86
95
|
end
|
|
87
96
|
|
|
88
|
-
end
|
|
97
|
+
end
|
|
@@ -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
|