loco_strings 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/lib/loco_strings/decoders/xcstrings_decoder.rb +106 -0
- data/lib/loco_strings/encoders/xcstrings_encoder.rb +83 -0
- data/lib/loco_strings/parsers/android_file.rb +1 -1
- data/lib/loco_strings/parsers/ios_file.rb +1 -1
- data/lib/loco_strings/parsers/loco_file.rb +1 -1
- data/lib/loco_strings/parsers/xcstrings_file.rb +60 -83
- data/lib/loco_strings/version.rb +1 -1
- data/lib/loco_strings.rb +33 -4
- metadata +5 -3
- data/loco_strings.gemspec +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36daf9f28f2ff56c824968da9a23422d2a3d50e8260154469cb93b8637097350
|
4
|
+
data.tar.gz: 948f62e57518bc34201c1f6ae67f66ca2f8a0b10662389caeaf908505a12e2e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '028dc48c692c84b7e6fc97b0bbd572faedb686a59c50084e2aa9bd0cc95b7f88d6a82cad905fd984a44bdf7a6b2de1166ba00a1390bf115131ca5ddb8af0246b'
|
7
|
+
data.tar.gz: a88259d18e7893c48bea667080de5d2ccd2ee4904fc7a83f7a219abcc70d47cf7a1004658a06b6f83673b2cea0c5532352e6712c30d6b3a599a0a7f256176c9c
|
data/.rubocop.yml
CHANGED
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module LocoStrings
|
6
|
+
# The XCStringsDecoder class is responsible for decoding the XCStrings file format.
|
7
|
+
class XCStringsDecoder
|
8
|
+
attr_reader :language, :strings, :translations, :languages
|
9
|
+
|
10
|
+
def initialize(file_path)
|
11
|
+
@file_path = file_path
|
12
|
+
@strings = {}
|
13
|
+
@translations = {}
|
14
|
+
@languages = []
|
15
|
+
@language = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def decode
|
19
|
+
return @strings unless File.exist?(@file_path)
|
20
|
+
|
21
|
+
file = File.read(@file_path)
|
22
|
+
json = JSON.parse(file)
|
23
|
+
extract_languages(json)
|
24
|
+
@language = json["sourceLanguage"]
|
25
|
+
decode_strings(json)
|
26
|
+
@strings
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def extract_languages(json)
|
32
|
+
@languages = []
|
33
|
+
json["strings"].each do |_key, value|
|
34
|
+
next unless value.key?("localizations")
|
35
|
+
|
36
|
+
value["localizations"].each do |lang, _loc|
|
37
|
+
@languages << lang
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@languages.uniq!
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode_strings(json)
|
44
|
+
strings = json["strings"]
|
45
|
+
strings.each do |key, value|
|
46
|
+
@strings[key] = decode_string(key, value, @language)
|
47
|
+
decode_translations(key, value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def decode_string(key, value, language)
|
52
|
+
return LocoString.new(key, key, value["comment"], "new") unless value.key?("localizations")
|
53
|
+
|
54
|
+
loc = value.dig("localizations", language)
|
55
|
+
return if loc.nil?
|
56
|
+
|
57
|
+
if loc.key?("stringUnit")
|
58
|
+
decode_string_unit(key, loc, value["comment"])
|
59
|
+
elsif loc.key?("variations")
|
60
|
+
decode_variations(key, loc, value["comment"])
|
61
|
+
else
|
62
|
+
LocoString.new(key, key, value["comment"], "new")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def decode_string_unit(key, value, comment)
|
67
|
+
return nil unless value.key?("stringUnit")
|
68
|
+
|
69
|
+
unit = value["stringUnit"]
|
70
|
+
translation = unit["value"]
|
71
|
+
LocoString.new(key, translation, comment, unit["state"]) unless translation.nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
def decode_variations(key, value, comment)
|
75
|
+
variations = value["variations"]
|
76
|
+
plural = decode_plural(variations, comment)
|
77
|
+
return nil if plural.empty?
|
78
|
+
|
79
|
+
variation = LocoVariantions.new(key, comment)
|
80
|
+
plural.each do |unit|
|
81
|
+
variation.append_string(unit)
|
82
|
+
end
|
83
|
+
variation
|
84
|
+
end
|
85
|
+
|
86
|
+
def decode_plural(variation, comment)
|
87
|
+
plural = variation["plural"]
|
88
|
+
return [] if plural.nil?
|
89
|
+
|
90
|
+
result = []
|
91
|
+
plural.each do |key, value|
|
92
|
+
unit = decode_string_unit(key, value, comment)
|
93
|
+
result << unit unless unit.nil?
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def decode_translations(key, value)
|
99
|
+
@languages.each do |language|
|
100
|
+
string = decode_string(key, value, language)
|
101
|
+
@translations[language] ||= {}
|
102
|
+
@translations[language][key] = string
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module LocoStrings
|
6
|
+
# The XCStringsEncoder class is responsible for encoding the LocoStrings data to the XCStrings format.
|
7
|
+
class XCStringsEncoder
|
8
|
+
attr_reader :language, :strings, :translations, :languages
|
9
|
+
|
10
|
+
def initialize(strings, translations, languages, language)
|
11
|
+
@strings = strings
|
12
|
+
@translations = translations
|
13
|
+
@languages = languages
|
14
|
+
@language = language
|
15
|
+
end
|
16
|
+
|
17
|
+
def encode
|
18
|
+
raise Error, "The base language is not defined" if @language.nil?
|
19
|
+
|
20
|
+
json = {
|
21
|
+
"sourceLanguage" => @language,
|
22
|
+
"strings" => {},
|
23
|
+
"version" => "1.0"
|
24
|
+
}
|
25
|
+
generate_keys.each do |key|
|
26
|
+
json["strings"][key] = encode_key(key)
|
27
|
+
end
|
28
|
+
JSON.pretty_generate(json, { space_before: " " })
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def generate_keys
|
34
|
+
keys = []
|
35
|
+
@translations.each do |_, translation|
|
36
|
+
keys += translation.keys
|
37
|
+
end
|
38
|
+
keys.uniq
|
39
|
+
end
|
40
|
+
|
41
|
+
def encode_key(key)
|
42
|
+
row = {}
|
43
|
+
@translations.each do |language, translation|
|
44
|
+
next unless translation.key?(key)
|
45
|
+
|
46
|
+
value = translation[key]
|
47
|
+
next if value.nil?
|
48
|
+
|
49
|
+
row["comment"] = value.comment unless row.key?("comment") || value.comment.nil?
|
50
|
+
row["localizations"] ||= {}
|
51
|
+
row["localizations"][language] = encode_value(value)
|
52
|
+
end
|
53
|
+
row
|
54
|
+
end
|
55
|
+
|
56
|
+
def encode_value(value)
|
57
|
+
if value.is_a?(LocoVariantions)
|
58
|
+
encode_variations(value)
|
59
|
+
else
|
60
|
+
encode_string_unit(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def encode_string_unit(unit)
|
65
|
+
res = { "stringUnit" => {} }
|
66
|
+
res["stringUnit"]["state"] = unit.state unless unit.state.nil?
|
67
|
+
res["stringUnit"]["value"] = unit.value
|
68
|
+
res
|
69
|
+
end
|
70
|
+
|
71
|
+
def encode_variations(variations)
|
72
|
+
plural = {}
|
73
|
+
variations.strings.each do |key, string|
|
74
|
+
plural[key] = encode_string_unit(string) unless string.nil?
|
75
|
+
end
|
76
|
+
{
|
77
|
+
"variations" => {
|
78
|
+
"plural" => plural
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -23,7 +23,7 @@ module LocoStrings
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def update(key, value, comment = nil)
|
26
|
-
comment = @strings[key].comment if comment.nil? && @strings.
|
26
|
+
comment = @strings[key].comment if comment.nil? && @strings.key?(key)
|
27
27
|
@strings[key] = LocoString.new key, value, comment
|
28
28
|
end
|
29
29
|
|
@@ -1,102 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "loco_file"
|
4
|
+
require_relative "../encoders/xcstrings_encoder"
|
5
|
+
require_relative "../decoders/xcstrings_decoder"
|
2
6
|
require "json"
|
3
7
|
|
4
8
|
module LocoStrings
|
9
|
+
# The XCStringsFile class is responsible for reading and writing the XCStrings file format.
|
5
10
|
class XCStringsFile < LocoFile
|
6
11
|
def read
|
7
12
|
clean
|
8
13
|
return @strings unless File.exist?(@file_path)
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
@translations =
|
16
|
-
|
17
|
-
strings.each do |key, value|
|
18
|
-
comment = value["comment"]
|
19
|
-
translation = key
|
20
|
-
if value.has_key?("localizations") && value["localizations"].has_key?(@language)
|
21
|
-
loc = value["localizations"][@language]
|
22
|
-
end
|
23
|
-
translation = loc["stringUnit"]["value"] unless loc.nil?
|
24
|
-
@strings[key] = LocoString.new key, translation, comment unless translation.nil?
|
25
|
-
for language in @languages
|
26
|
-
next unless value.has_key?("localizations") && value["localizations"].has_key?(language)
|
27
|
-
|
28
|
-
loc = value["localizations"][language]
|
29
|
-
translation = loc["stringUnit"]["value"] unless loc.nil?
|
30
|
-
@translations[language] = {} unless @translations.has_key?(language)
|
31
|
-
@translations[language][key] = translation unless translation.nil?
|
32
|
-
end
|
33
|
-
end
|
15
|
+
decoder = XCStringsDecoder.new(@file_path)
|
16
|
+
decoder.decode
|
17
|
+
|
18
|
+
@language = decoder.language
|
19
|
+
@strings = decoder.strings
|
20
|
+
@translations = decoder.translations
|
21
|
+
@languages = decoder.languages
|
34
22
|
@strings
|
35
23
|
end
|
36
24
|
|
37
25
|
def write
|
38
26
|
raise Error, "The base language is not defined" if @language.nil?
|
39
27
|
|
40
|
-
json =
|
41
|
-
json
|
42
|
-
json["strings"] = {}
|
43
|
-
@strings.each do |key, value|
|
44
|
-
row = {}
|
45
|
-
row["comment"] = value.comment unless value.comment.nil?
|
46
|
-
localizations = {}
|
47
|
-
if value.value != key && !value.value.nil?
|
48
|
-
localizations[@language] = {
|
49
|
-
"stringUnit" => {
|
50
|
-
"state" => "translated",
|
51
|
-
"value" => value.value
|
52
|
-
}
|
53
|
-
}
|
54
|
-
end
|
55
|
-
@languages.each do |language|
|
56
|
-
next if language == @language
|
57
|
-
next unless @translations.has_key?(language) && @translations[language].has_key?(key)
|
58
|
-
|
59
|
-
localizations[language] = {
|
60
|
-
"stringUnit" => {
|
61
|
-
"state" => "translated",
|
62
|
-
"value" => @translations[language][key]
|
63
|
-
}
|
64
|
-
}
|
65
|
-
end
|
66
|
-
row["localizations"] = localizations unless localizations.empty?
|
67
|
-
json["strings"][key] = row
|
68
|
-
end
|
69
|
-
json["version"] = "1.0"
|
70
|
-
File.open(@file_path, "w") { |file| file.write(JSON.pretty_generate(json)) }
|
28
|
+
json = XCStringsEncoder.new(@strings, @translations, @languages, @language).encode
|
29
|
+
File.write(@file_path, json)
|
71
30
|
end
|
72
31
|
|
73
|
-
def update(key, value, comment = nil, language = @language)
|
32
|
+
def update(key, value, comment = nil, stage = nil, language = @language)
|
74
33
|
raise Error, "The base language is not defined" if @language.nil?
|
75
34
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@translations[language][key] = value
|
80
|
-
@strings[key] = LocoString.new key, key, comment unless @strings.has_key?(key)
|
81
|
-
return
|
82
|
-
end
|
83
|
-
comment = @strings[key].comment if @strings.has_key?(key) && (comment.nil? && @strings.has_key?(key))
|
84
|
-
@strings[key] = LocoString.new key, value, comment
|
85
|
-
end
|
35
|
+
stage = "translated" if stage.nil?
|
36
|
+
string = make_strings(key, value, comment, stage, language)
|
37
|
+
return if string.nil?
|
86
38
|
|
87
|
-
|
88
|
-
|
89
|
-
|
39
|
+
@translations[language] ||= {}
|
40
|
+
@translations[language][key] = string
|
41
|
+
@strings[key] = string if @language == language
|
90
42
|
end
|
91
43
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
44
|
+
def update_variation(key, variant, strings, comment = nil, state = nil, language = @language) # rubocop:disable Metrics/ParameterLists
|
45
|
+
raise Error, "The base language is not defined" if @language.nil?
|
46
|
+
|
47
|
+
variations = make_variations(key, variant, strings, comment, state, language)
|
48
|
+
return if variations.nil?
|
49
|
+
|
50
|
+
@translations[language] ||= {}
|
51
|
+
@translations[language][key] = variations
|
52
|
+
@strings[key] = variations if @language == language
|
97
53
|
end
|
98
54
|
|
99
|
-
def
|
55
|
+
def select_language(language)
|
100
56
|
raise Error, "The base language is aready defined" unless @language.nil?
|
101
57
|
|
102
58
|
@language = language
|
@@ -113,18 +69,39 @@ module LocoStrings
|
|
113
69
|
result
|
114
70
|
end
|
115
71
|
|
72
|
+
def clean
|
73
|
+
@strings = {}
|
74
|
+
@translations = {}
|
75
|
+
@languages = []
|
76
|
+
@language = nil
|
77
|
+
end
|
78
|
+
|
116
79
|
private
|
117
80
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
next unless value.has_key?("localizations")
|
81
|
+
def make_strings(key, value, comment = nil, state = nil, language = @language)
|
82
|
+
unit = @translations.dig(language, key)
|
83
|
+
return LocoString.new(key, value, comment, state) if unit.nil?
|
122
84
|
|
123
|
-
|
124
|
-
|
125
|
-
|
85
|
+
if unit.is_a?(LocoVariantions)
|
86
|
+
puts "Variations not supported through this method"
|
87
|
+
return nil
|
126
88
|
end
|
127
|
-
|
89
|
+
unit.update(value, comment, state)
|
90
|
+
unit
|
91
|
+
end
|
92
|
+
|
93
|
+
def make_variations(key, variant, value, comment = nil, state = nil, language = @language) # rubocop:disable Metrics/ParameterLists
|
94
|
+
variants = @translations.dig(language, key)
|
95
|
+
|
96
|
+
return make_strings(key, value, comment, state, language) if variants.is_a?(LocoString)
|
97
|
+
|
98
|
+
if variants.nil?
|
99
|
+
state = "new" if state.nil?
|
100
|
+
return LocoVariantions.new(key, { variant => LocoString.new(variant, value, comment, state) })
|
101
|
+
end
|
102
|
+
|
103
|
+
variants.update_variant(variant, value, comment, state)
|
104
|
+
variants
|
128
105
|
end
|
129
106
|
end
|
130
107
|
end
|
data/lib/loco_strings/version.rb
CHANGED
data/lib/loco_strings.rb
CHANGED
@@ -9,17 +9,46 @@ require_relative "loco_strings/parsers/xcstrings_file"
|
|
9
9
|
module LocoStrings
|
10
10
|
class Error < StandardError; end
|
11
11
|
|
12
|
-
LocoString = Struct.new(:key, :value, :comment) do
|
13
|
-
def initialize(key, value, comment = nil)
|
12
|
+
LocoString = Struct.new(:key, :value, :comment, :state) do
|
13
|
+
def initialize(key, value, comment = nil, state = nil)
|
14
14
|
super
|
15
15
|
end
|
16
16
|
|
17
|
+
def update(value, comment = nil, state = nil)
|
18
|
+
self.value = value
|
19
|
+
self.comment = comment unless comment.nil?
|
20
|
+
self.state = state
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Key: #{key}, Value: #{value}, Comment: #{comment || "None"}, State: #{state || "None"}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
LocoVariantions = Struct.new(:key, :strings, :comment) do
|
29
|
+
def initialize(key, strings = nil, comment = nil)
|
30
|
+
super
|
31
|
+
self.strings = strings || {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def append_string(string)
|
35
|
+
strings[string.key] = string
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_variant(key, value, comment = nil, state = nil)
|
39
|
+
if strings.key? key
|
40
|
+
strings[key].update(value, comment, state)
|
41
|
+
else
|
42
|
+
strings[key] = LocoString.new(key, value, comment, state)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
17
46
|
def to_s
|
18
|
-
"Key: #{key},
|
47
|
+
"Key: #{key}, Strings: #{strings}, Comment: #{comment || "None"}"
|
19
48
|
end
|
20
49
|
end
|
21
50
|
|
22
|
-
def self.load(file_path)
|
51
|
+
def self.load(file_path) # rubocop:disable Metrics/MethodLength
|
23
52
|
ext = File.extname(file_path)
|
24
53
|
raise Error, "Unsupported file format: #{ext}" unless [".strings", ".xml", ".xcstrings"].include? ext
|
25
54
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loco_strings
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksei Cherepanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -53,12 +53,13 @@ files:
|
|
53
53
|
- README.md
|
54
54
|
- Rakefile
|
55
55
|
- lib/loco_strings.rb
|
56
|
+
- lib/loco_strings/decoders/xcstrings_decoder.rb
|
57
|
+
- lib/loco_strings/encoders/xcstrings_encoder.rb
|
56
58
|
- lib/loco_strings/parsers/android_file.rb
|
57
59
|
- lib/loco_strings/parsers/ios_file.rb
|
58
60
|
- lib/loco_strings/parsers/loco_file.rb
|
59
61
|
- lib/loco_strings/parsers/xcstrings_file.rb
|
60
62
|
- lib/loco_strings/version.rb
|
61
|
-
- loco_strings.gemspec
|
62
63
|
- sig/loco_strings.rbs
|
63
64
|
homepage: https://github.com/ftp27/loco_strings
|
64
65
|
licenses:
|
@@ -66,6 +67,7 @@ licenses:
|
|
66
67
|
metadata:
|
67
68
|
homepage_uri: https://github.com/ftp27/loco_strings
|
68
69
|
source_code_uri: https://github.com/ftp27/loco_strings
|
70
|
+
rubygems_mfa_required: 'true'
|
69
71
|
post_install_message:
|
70
72
|
rdoc_options: []
|
71
73
|
require_paths:
|
data/loco_strings.gemspec
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# rubocop:disable Layout/LineLength
|
4
|
-
|
5
|
-
require_relative "lib/loco_strings/version"
|
6
|
-
|
7
|
-
Gem::Specification.new do |spec|
|
8
|
-
spec.name = "loco_strings"
|
9
|
-
spec.version = LocoStrings::VERSION
|
10
|
-
spec.authors = ["Aleksei Cherepanov"]
|
11
|
-
spec.email = ["ftp27host@gmail.com"]
|
12
|
-
|
13
|
-
spec.summary = "LocoStrings is a Ruby gem for working with iOS and Android localization strings."
|
14
|
-
spec.description = "LocoStrings is a powerful and easy-to-use Ruby gem that simplifies the process of managing localization strings for iOS and Android apps. With LocoStrings, you can easily extract, organize, and update your app's localized strings in one place, making it easy to maintain consistency across all of your app's translations. LocoStrings supports multiple file formats, including XLIFF and CSV, and provides a simple and intuitive API for working with your app's strings in code. Whether you're a solo developer or part of a team, LocoStrings makes managing your app's localization a breeze."
|
15
|
-
spec.homepage = "https://github.com/ftp27/loco_strings"
|
16
|
-
spec.license = "MIT"
|
17
|
-
spec.required_ruby_version = ">= 2.6.0"
|
18
|
-
|
19
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
21
|
-
|
22
|
-
# Specify which files should be added to the gem when it is released.
|
23
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
-
spec.files = Dir.chdir(__dir__) do
|
25
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
-
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
27
|
-
end
|
28
|
-
end
|
29
|
-
spec.bindir = "exe"
|
30
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = ["lib"]
|
32
|
-
|
33
|
-
# Uncomment to register a new dependency of your gem
|
34
|
-
spec.add_dependency "nokogiri", "~> 1.13", ">= 1.13.8"
|
35
|
-
|
36
|
-
# For more information and examples about making a new gem, check out our
|
37
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
38
|
-
end
|
39
|
-
|
40
|
-
# rubocop:enable Layout/LineLength
|