loco_strings 0.1.2 → 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/Gemfile.lock +1 -1
- 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 +90 -52
- data/lib/loco_strings/version.rb +1 -1
- data/lib/loco_strings.rb +37 -6
- 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
data/Gemfile.lock
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,69 +1,107 @@
|
|
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
|
-
def initialize(file_path, language)
|
7
|
-
@file_path = file_path
|
8
|
-
@language = language
|
9
|
-
clean
|
10
|
-
end
|
11
|
-
|
12
11
|
def read
|
13
12
|
clean
|
14
13
|
return @strings unless File.exist?(@file_path)
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
strings =
|
21
|
-
|
22
|
-
|
23
|
-
translation = key unless @language != sourceLanguage
|
24
|
-
if value.has_key?("localizations") && value["localizations"].has_key?(@language)
|
25
|
-
loc = value["localizations"][@language]
|
26
|
-
end
|
27
|
-
translation = loc["stringUnit"]["value"] unless loc.nil?
|
28
|
-
@strings[key] = LocoString.new key, translation, comment unless translation.nil?
|
29
|
-
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
|
30
22
|
@strings
|
31
23
|
end
|
32
24
|
|
33
|
-
def write
|
34
|
-
|
35
|
-
|
36
|
-
json
|
25
|
+
def write
|
26
|
+
raise Error, "The base language is not defined" if @language.nil?
|
27
|
+
|
28
|
+
json = XCStringsEncoder.new(@strings, @translations, @languages, @language).encode
|
29
|
+
File.write(@file_path, json)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update(key, value, comment = nil, stage = nil, language = @language)
|
33
|
+
raise Error, "The base language is not defined" if @language.nil?
|
34
|
+
|
35
|
+
stage = "translated" if stage.nil?
|
36
|
+
string = make_strings(key, value, comment, stage, language)
|
37
|
+
return if string.nil?
|
38
|
+
|
39
|
+
@translations[language] ||= {}
|
40
|
+
@translations[language][key] = string
|
41
|
+
@strings[key] = string if @language == language
|
42
|
+
end
|
43
|
+
|
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
|
53
|
+
end
|
54
|
+
|
55
|
+
def select_language(language)
|
56
|
+
raise Error, "The base language is aready defined" unless @language.nil?
|
57
|
+
|
58
|
+
@language = language
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
result = ""
|
63
|
+
result += "Base language: #{@language}\n" unless @language.nil?
|
64
|
+
result += "Languages: #{@languages}\n" unless @languages.empty?
|
65
|
+
result += "Strings:\n"
|
37
66
|
@strings.each do |key, value|
|
38
|
-
|
39
|
-
row["comment"] = value.comment unless value.comment.nil?
|
40
|
-
localizations = {}
|
41
|
-
if value.value != key && !value.value.nil?
|
42
|
-
localizations[@language] = {
|
43
|
-
"stringUnit" => {
|
44
|
-
"state" => "translated",
|
45
|
-
"value" => value.value
|
46
|
-
}
|
47
|
-
}
|
48
|
-
end
|
49
|
-
files.each do |file|
|
50
|
-
translation = file.value(key)
|
51
|
-
next unless translation
|
52
|
-
|
53
|
-
next unless key != translation
|
54
|
-
|
55
|
-
localizations[file.language] = {
|
56
|
-
"stringUnit" => {
|
57
|
-
"state" => "translated",
|
58
|
-
"value" => translation
|
59
|
-
}
|
60
|
-
}
|
61
|
-
end
|
62
|
-
row["localizations"] = localizations unless localizations.empty?
|
63
|
-
json["strings"][key] = row
|
67
|
+
result += "#{key}: #{value}\n"
|
64
68
|
end
|
65
|
-
|
66
|
-
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
def clean
|
73
|
+
@strings = {}
|
74
|
+
@translations = {}
|
75
|
+
@languages = []
|
76
|
+
@language = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
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?
|
84
|
+
|
85
|
+
if unit.is_a?(LocoVariantions)
|
86
|
+
puts "Variations not supported through this method"
|
87
|
+
return nil
|
88
|
+
end
|
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
|
67
105
|
end
|
68
106
|
end
|
69
107
|
end
|
data/lib/loco_strings/version.rb
CHANGED
data/lib/loco_strings.rb
CHANGED
@@ -9,13 +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
|
+
|
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
|
16
26
|
end
|
17
27
|
|
18
|
-
|
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
|
+
|
46
|
+
def to_s
|
47
|
+
"Key: #{key}, Strings: #{strings}, Comment: #{comment || "None"}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.load(file_path) # rubocop:disable Metrics/MethodLength
|
19
52
|
ext = File.extname(file_path)
|
20
53
|
raise Error, "Unsupported file format: #{ext}" unless [".strings", ".xml", ".xcstrings"].include? ext
|
21
54
|
|
@@ -25,9 +58,7 @@ module LocoStrings
|
|
25
58
|
when ".xml"
|
26
59
|
AndroidFile.new file_path
|
27
60
|
when ".xcstrings"
|
28
|
-
|
29
|
-
|
30
|
-
XCStringsFile.new file_path, language
|
61
|
+
XCStringsFile.new file_path
|
31
62
|
else
|
32
63
|
raise Error, "Not implemented"
|
33
64
|
end
|
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
|