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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ae94b18e40b03e455e87bd4c5a4a4b2139ee822e0f67fb7bddd4f525027a936
4
- data.tar.gz: f4decc1b1751dc3f9cdafdf7db9ac9e950f344e3990c2ed7db9373238e760193
3
+ metadata.gz: 36daf9f28f2ff56c824968da9a23422d2a3d50e8260154469cb93b8637097350
4
+ data.tar.gz: 948f62e57518bc34201c1f6ae67f66ca2f8a0b10662389caeaf908505a12e2e5
5
5
  SHA512:
6
- metadata.gz: 7be956a59f8a41eabf7a7196cd03e0979f411b4f511c43c0c4a607af32b680e85b1e7d210445070786f43024d9eabcfe7b6c504283b93c1ddc1ca2206cd7b36e
7
- data.tar.gz: e80e535b831507b88eeb25e9d392ca4f39b91112c73d5f2074f69965cacbb8109d0cf3806694ed9164ce5cee417849e4ed13130885edd5efdab9b9dd6181fd2c
6
+ metadata.gz: '028dc48c692c84b7e6fc97b0bbd572faedb686a59c50084e2aa9bd0cc95b7f88d6a82cad905fd984a44bdf7a6b2de1166ba00a1390bf115131ca5ddb8af0246b'
7
+ data.tar.gz: a88259d18e7893c48bea667080de5d2ccd2ee4904fc7a83f7a219abcc70d47cf7a1004658a06b6f83673b2cea0c5532352e6712c30d6b3a599a0a7f256176c9c
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.1)
4
+ loco_strings (0.1.3)
5
5
  nokogiri (~> 1.13, >= 1.13.8)
6
6
 
7
7
  GEM
@@ -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
@@ -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,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
- comment = nil
17
- file = File.read(@file_path)
18
- json = JSON.parse(file)
19
- sourceLanguage = json["sourceLanguage"]
20
- strings = json["strings"]
21
- strings.each do |key, value|
22
- comment = value["comment"]
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(files = [])
34
- json = {}
35
- json["sourceLanguage"] = @language
36
- json["strings"] = {}
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
- row = {}
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
- json["version"] = "1.0"
66
- File.open(@file_path, "w") { |file| file.write(JSON.pretty_generate(json)) }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LocoStrings
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
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
- def self.load(file_path, language = nil)
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
- raise Error, "Language is required for xcstrings files" unless language
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.2
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: 2023-11-16 00:00:00.000000000 Z
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