loco_strings 0.1.3 → 0.1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 567e3981149f49dd55a99138ca51993030011f886c227f3cd8809c5a0c1e48c5
4
- data.tar.gz: d9424623eeb635a2798a4f858701be4fa1897714af4181caa5863dbcb8d4bf4d
3
+ metadata.gz: c2d39a13c5afee6bd306c83aee1793452203d521b426645f1fa885b4b3da1e29
4
+ data.tar.gz: bd6c23d23e3fb41c39d213f5f33b4f787cf4bea2e323a46b018382a402ca98ec
5
5
  SHA512:
6
- metadata.gz: 50404d9b25812983c3b6f537d4220572f7df87c837b19411fd22293cc6c1abae4550ec3e630bc359853b8092802cf8324a2b4db8b753ba830d20e0d42bae82c3
7
- data.tar.gz: f650a177e645204d68e6cd65fa87214976b5c258c266d90bcdf61a1061bd49466a442dd74d3bac22b1fe7a86b13ecacedb90a32bff5fbf0db3e4c70fc4dd63e4
6
+ metadata.gz: 90f78c9ba808022d66e4495906c67699023cf224b552ad67b28db15ef040d1da3afabb7b72423630af5d46d17f857fa77cae032881669b222e6d17cb812d8aea
7
+ data.tar.gz: 65d4ee5ce0b91ba6e084e0ad84942e88a499476d334d5b8990e097312983b5a0ecd4a153275ff953fe9704f72df69a83320fe02694ac8357e7b3b3130ba39aab
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.6
3
+ NewCops: enable
3
4
 
4
5
  Style/StringLiterals:
5
6
  Enabled: true
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- loco_strings (0.1.3)
4
+ loco_strings (0.1.4.1)
5
5
  nokogiri (~> 1.13, >= 1.13.8)
6
6
 
7
7
  GEM
@@ -0,0 +1,107 @@
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
+ val = decode_string(key, value, @language)
47
+ @strings[key] = val if val
48
+ @strings[key] = LocoString.new(key, key, value["comment"], "new") if val.nil?
49
+
50
+ decode_translations(key, value)
51
+ end
52
+ end
53
+
54
+ def decode_string(key, value, language)
55
+ return unless value.key?("localizations")
56
+
57
+ loc = value.dig("localizations", language)
58
+ return if loc.nil?
59
+
60
+ if loc.key?("stringUnit")
61
+ decode_string_unit(key, loc, value["comment"])
62
+ elsif loc.key?("variations")
63
+ decode_variations(key, loc, value["comment"])
64
+ end
65
+ end
66
+
67
+ def decode_string_unit(key, value, comment)
68
+ return nil unless value.key?("stringUnit")
69
+
70
+ unit = value["stringUnit"]
71
+ translation = unit["value"]
72
+ LocoString.new(key, translation, comment, unit["state"]) unless translation.nil?
73
+ end
74
+
75
+ def decode_variations(key, value, comment)
76
+ variations = value["variations"]
77
+ plural = decode_plural(variations, comment)
78
+ return nil if plural.empty?
79
+
80
+ variation = LocoVariantions.new(key, comment)
81
+ plural.each do |unit|
82
+ variation.append_string(unit)
83
+ end
84
+ variation
85
+ end
86
+
87
+ def decode_plural(variation, comment)
88
+ plural = variation["plural"]
89
+ return [] if plural.nil?
90
+
91
+ result = []
92
+ plural.each do |key, value|
93
+ unit = decode_string_unit(key, value, comment)
94
+ result << unit unless unit.nil?
95
+ end
96
+ result
97
+ end
98
+
99
+ def decode_translations(key, value)
100
+ @languages.each do |language|
101
+ string = decode_string(key, value, language)
102
+ @translations[language] ||= {}
103
+ @translations[language][key] = string
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,87 @@
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"] = if unit.state.nil?
67
+ "new"
68
+ else
69
+ unit.state
70
+ end
71
+ res["stringUnit"]["value"] = unit.value
72
+ res
73
+ end
74
+
75
+ def encode_variations(variations)
76
+ plural = {}
77
+ variations.strings.each do |key, string|
78
+ plural[key] = encode_string_unit(string) unless string.nil?
79
+ end
80
+ {
81
+ "variations" => {
82
+ "plural" => plural
83
+ }
84
+ }
85
+ end
86
+ end
87
+ end
@@ -29,7 +29,7 @@ module LocoStrings
29
29
  end
30
30
  end
31
31
  end
32
- File.open(@file_path, "w") { |file| file.write(builder.to_xml) }
32
+ File.write(@file_path, builder.to_xml)
33
33
  end
34
34
 
35
35
  private
@@ -25,7 +25,7 @@ module LocoStrings
25
25
  output += "/* #{value.comment} */\n" if value.comment
26
26
  output += "\"#{key}\" = \"#{value.value}\";\n"
27
27
  end
28
- File.open(@file_path, "w") { |file| file.write(output) }
28
+ File.write(@file_path, output)
29
29
  end
30
30
 
31
31
  private
@@ -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.has_key?(key)
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,107 +1,84 @@
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
- comment = nil
11
- file = File.read(@file_path)
12
- json = JSON.parse(file)
13
- extract_languages(json)
14
- @language = json["sourceLanguage"]
15
- @translations = {}
16
- strings = json["strings"]
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["sourceLanguage"] = @language
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)
74
- raise Error, "The base language is not defined" if @language.nil?
32
+ def update(key, value, comment = nil, stage = nil, language = @language)
33
+ raise Error, "The base language is not defined" if language.nil?
75
34
 
76
- if @language != language
77
- @languages << language unless @languages.include?(language)
78
- @translations[language] = {} unless @translations.has_key?(language)
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
- def value(key, language = nil)
88
- return @strings[key] if language.nil? || language == @language
89
- return @translations[language][key] if @translations.has_key?(language) && @translations[language].has_key?(key)
39
+ @translations[language] ||= {}
40
+ @translations[language][key] = string
41
+ @strings[key] = string if @language == language
90
42
  end
91
43
 
92
- def clean
93
- @strings = {}
94
- @translations = {}
95
- @languages = []
96
- @language = nil
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 set_language(language)
55
+ def select_language(language)
100
56
  raise Error, "The base language is aready defined" unless @language.nil?
101
57
 
102
58
  @language = language
103
59
  end
104
60
 
61
+ def value(key)
62
+ raise Error, "The base language is not defined" if @language.nil?
63
+
64
+ value_by_language(key, @language)
65
+ end
66
+
67
+ def value_by_language(key, language)
68
+ str = @translations.dig(language, key)
69
+ return nil if str.nil?
70
+
71
+ if str.is_a?(LocoString)
72
+ str.value
73
+ elsif str.is_a?(LocoVariantions)
74
+ str.strings.map { |_, v| v.value }
75
+ end
76
+ end
77
+
78
+ def unit(key, language = @language)
79
+ @translations.dig(language, key)
80
+ end
81
+
105
82
  def to_s
106
83
  result = ""
107
84
  result += "Base language: #{@language}\n" unless @language.nil?
@@ -113,18 +90,39 @@ module LocoStrings
113
90
  result
114
91
  end
115
92
 
93
+ def clean
94
+ @strings = {}
95
+ @translations = {}
96
+ @languages = []
97
+ @language = nil
98
+ end
99
+
116
100
  private
117
101
 
118
- def extract_languages(json)
119
- @languages = []
120
- json["strings"].each do |_key, value|
121
- next unless value.has_key?("localizations")
102
+ def make_strings(key, value, comment = nil, state = nil, language = @language)
103
+ unit = @translations.dig(language, key)
104
+ return LocoString.new(key, value, comment, state) if unit.nil?
105
+
106
+ if unit.is_a?(LocoVariantions)
107
+ puts "Variations not supported through this method"
108
+ return nil
109
+ end
110
+ unit.update(value, comment, state)
111
+ unit
112
+ end
113
+
114
+ def make_variations(key, variant, value, comment = nil, state = nil, language = @language) # rubocop:disable Metrics/ParameterLists
115
+ variants = @translations.dig(language, key)
122
116
 
123
- value["localizations"].each do |lang, _loc|
124
- @languages << lang
125
- end
117
+ return make_strings(key, value, comment, state, language) if variants.is_a?(LocoString)
118
+
119
+ if variants.nil?
120
+ state = "new" if state.nil?
121
+ return LocoVariantions.new(key, { variant => LocoString.new(variant, value, comment, state) })
126
122
  end
127
- @languages.uniq!
123
+
124
+ variants.update_variant(variant, value, comment, state)
125
+ variants
128
126
  end
129
127
  end
130
128
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LocoStrings
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4.1"
5
5
  end
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}, Value: #{value}, Comment: #{comment || "None"}"
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.3
4
+ version: 0.1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleksei Cherepanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-16 00:00:00.000000000 Z
11
+ date: 2024-02-26 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