singleton-client-test 0.7.0.33 → 0.7.0.36

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.
@@ -0,0 +1,51 @@
1
+ # Copyright 2022 VMware, Inc.
2
+ # SPDX-License-Identifier: EPL-2.0
3
+
4
+ require 'json'
5
+ require 'pathname'
6
+
7
+ module SgtnClient
8
+ module Common
9
+ autoload :BundleID, 'sgtn-client/common/data'
10
+ end
11
+
12
+ module TranslationLoader
13
+ autoload :CONSTS, 'sgtn-client/loader/consts'
14
+
15
+ class LocalTranslation
16
+ BUNDLE_PREFIX = 'messages_'.freeze
17
+ BUNDLE_SUFFIX = '.json'.freeze
18
+
19
+ def initialize(config)
20
+ @base_path = Pathname.new(config['translation_bundle']) + config['product_name'] + config['version'].to_s
21
+ end
22
+
23
+ def load_bundle(component, locale)
24
+ return if locale == CONSTS::REAL_SOURCE_LOCALE # return when querying source
25
+
26
+ SgtnClient.logger.debug "[#{method(__callee__).owner}.#{__callee__}] component=#{component}, locale=#{locale}"
27
+
28
+ file_name = BUNDLE_PREFIX + locale + BUNDLE_SUFFIX
29
+ file_path = @base_path + component + file_name
30
+
31
+ bundle_data = JSON.parse(File.read(file_path))
32
+ messages = bundle_data['messages']
33
+
34
+ raise SgtnClient::SingletonError, "no messages in local bundle file: #{file_path}." unless messages
35
+
36
+ messages
37
+ end
38
+
39
+ def available_bundles
40
+ SgtnClient.logger.debug "[#{method(__callee__).owner}.#{__callee__}]"
41
+
42
+ @available_bundles ||= begin
43
+ @base_path.glob('*/*.json').reduce(Set.new) do |bundles, f|
44
+ locale = f.basename.to_s.sub(/\A#{BUNDLE_PREFIX}/i, '').sub(/#{BUNDLE_SUFFIX}\z/i, '')
45
+ bundles.add Common::BundleID.new(f.parent.basename.to_s, locale)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2022 VMware, Inc.
4
+ # SPDX-License-Identifier: EPL-2.0
5
+
6
+ require 'faraday'
7
+ require 'faraday_middleware'
8
+
9
+ module SgtnClient
10
+ module Common
11
+ autoload :BundleID, 'sgtn-client/common/data'
12
+ end
13
+
14
+ module TranslationLoader
15
+ autoload :CONSTS, 'sgtn-client/loader/consts'
16
+
17
+ class SgtnServer
18
+ ERROR_ILLEGAL_DATA = 'server returned illegal data.'
19
+ ERROR_BUSINESS_ERROR = 'server returned business error.'
20
+
21
+ REQUEST_ARGUMENTS = { timeout: 10 }.freeze
22
+
23
+ def initialize(config)
24
+ @server_url = config['vip_server']
25
+
26
+ product_root = format('/i18n/api/v2/translation/products/%s/versions/%s', config['product_name'], config['version'])
27
+
28
+ @bundle_url = "#{product_root}/locales/%s/components/%s"
29
+ @locales_url = "#{product_root}/localelist"
30
+ @components_url = "#{product_root}/componentlist"
31
+ end
32
+
33
+ def load_bundle(component, locale)
34
+ SgtnClient.logger.debug "[#{method(__callee__).owner}.#{__callee__}] component=#{component}, locale=#{locale}"
35
+
36
+ messages = query_server(format(@bundle_url, locale, component), ['messages'])
37
+ messages
38
+ end
39
+
40
+ def available_bundles
41
+ SgtnClient.logger.debug "[#{method(__callee__).owner}.#{__callee__}]"
42
+
43
+ components_thread = Thread.new { available_components }
44
+ available_locales.reduce(Set.new) do |bundles, locale|
45
+ components_thread.value.reduce(bundles) do |inner_bundles, component|
46
+ inner_bundles << Common::BundleID.new(component, locale)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def available_locales
54
+ query_server(@locales_url, ['locales'])
55
+ end
56
+
57
+ def available_components
58
+ query_server(@components_url, ['components'])
59
+ end
60
+
61
+ def query_server(url, path_to_data = [], queries = nil, headers = nil)
62
+ conn = Faraday.new(@server_url, request: REQUEST_ARGUMENTS) do |f|
63
+ f.response :json # decode response bodies as JSON
64
+ f.use :gzip
65
+ f.response :raise_error
66
+ f.response :logger, SgtnClient.logger, { log_level: :debug }
67
+ end
68
+ resp = conn.get(url, queries, headers)
69
+
70
+ process_business_error(resp.body)
71
+ extract_data(resp.body, path_to_data)
72
+ end
73
+
74
+ def extract_data(parsedbody, path_to_data)
75
+ data = parsedbody.dig('data', *path_to_data)
76
+ raise SgtnClient::SingletonError, "no expected data in response. Body is: #{parsedbody}" unless data
77
+
78
+ data
79
+ end
80
+
81
+ def process_business_error(parsedbody)
82
+ b_code = parsedbody.dig('response', 'code')
83
+ unless b_code >= 200 && b_code < 300 || b_code >= 600 && b_code < 700
84
+ raise SgtnClient::SingletonError, "#{ERROR_BUSINESS_ERROR} #{parsedbody['response']}"
85
+ end
86
+
87
+ # 600 means a successful response, 6xx means partial successful.
88
+ SgtnClient.logger.warn "#{ERROR_BUSINESS_ERROR} #{parsedbody['response']}" if b_code > 600
89
+ rescue TypeError, ArgumentError, NoMethodError => e
90
+ raise SgtnClient::SingletonError, "#{ERROR_ILLEGAL_DATA} #{e}. Body is: #{parsedbody}"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright 2022 VMware, Inc.
2
+ # SPDX-License-Identifier: EPL-2.0
3
+
4
+ module SgtnClient
5
+ autoload :SingleOperation, 'sgtn-client/common/single_operation'
6
+ autoload :CacheUtil, 'sgtn-client/util/cache-util'
7
+
8
+ module TranslationLoader
9
+ autoload :CONSTS, 'sgtn-client/loader/consts'
10
+
11
+ module SingleLoader
12
+ def load_bundle(component, locale)
13
+ SgtnClient.logger.debug "[#{__FILE__}][#{__callee__}] component=#{component}, locale=#{locale}"
14
+
15
+ @single_bundle_loader ||= single_loader { |c,l| super(c,l) }
16
+ id = CacheUtil.get_cachekey(component, locale)
17
+ @single_bundle_loader.operate(id, component, locale)&.value
18
+ ensure
19
+ # delete thread from hash after finish
20
+ @single_bundle_loader.remove_object(id)
21
+ end
22
+
23
+ def available_bundles
24
+ SgtnClient.logger.debug "[#{__FILE__}][#{__callee__}]"
25
+
26
+ @single_available_bundles_loader ||= single_loader { super }
27
+ @single_available_bundles_loader.operate(CONSTS::AVAILABLE_BUNDLES_KEY)&.value
28
+ ensure
29
+ @single_available_bundles_loader.remove_object(CONSTS::AVAILABLE_BUNDLES_KEY)
30
+ end
31
+
32
+ private
33
+ def single_loader(&block)
34
+ none_alive = proc { |_, thread| thread.nil? || thread.alive? == false }
35
+ creator = proc do |id, _, *args|
36
+ Thread.new do
37
+ SgtnClient.logger.debug "start single loading #{id}"
38
+ block.call(*args)
39
+ end
40
+ end
41
+
42
+ SgtnClient::SingleOperation.new(none_alive, &creator)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,55 @@
1
+ # Copyright 2022 VMware, Inc.
2
+ # SPDX-License-Identifier: EPL-2.0
3
+
4
+ require 'pathname'
5
+
6
+ module SgtnClient
7
+ module Common
8
+ autoload :BundleID, 'sgtn-client/common/data'
9
+ end
10
+
11
+ module TranslationLoader
12
+ autoload :CONSTS, 'sgtn-client/loader/consts'
13
+
14
+ class Source
15
+ def initialize(config)
16
+ @source_bundle_path = Pathname.new(config['source_bundle'])
17
+ end
18
+
19
+ def load_bundle(component, locale = nil)
20
+ return if locale && locale != CONSTS::REAL_SOURCE_LOCALE # return when NOT querying source
21
+
22
+ SgtnClient.logger.debug "[#{method(__callee__).owner}.#{__callee__}] component=#{component}"
23
+
24
+ total_messages = {}
25
+
26
+ (@source_bundle_path + component).glob('**/*.{yml, yaml}') do |f|
27
+ bundle = YAML.safe_load(File.read(f))
28
+ messages = bundle&.first&.last # TODO: Warn about inconsistent source locale
29
+ if messages.is_a?(Hash)
30
+ total_messages.merge!(messages)
31
+ else
32
+ SgtnClient.logger.error "[#{method(__callee__).owner}.#{__callee__}] invalid bundle data in #{f}"
33
+ end
34
+ end
35
+
36
+ raise SgtnClient::SingletonError, "no local source messages for component #{component}" if total_messages.empty?
37
+
38
+ total_messages
39
+ end
40
+
41
+ def available_bundles
42
+ SgtnClient.logger.debug "[#{method(__callee__).owner}.#{__callee__}]"
43
+
44
+ @available_bundles ||= begin
45
+ @source_bundle_path.children.select(&:directory?).reduce(Set.new) do |bundles, component|
46
+ component.glob('**/*.{yml, yaml}') do |_|
47
+ bundles << Common::BundleID.new(component.basename.to_s, SgtnClient::LocaleUtil.get_source_locale)
48
+ break bundles
49
+ end || bundles
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -2,37 +2,57 @@
2
2
  # SPDX-License-Identifier: EPL-2.0
3
3
 
4
4
  module SgtnClient
5
- autoload :Source, 'sgtn-client/loader/local_source_bundle'
6
- end
7
- module SgtnClient::TranslationLoader::SourceComparer
8
- def load_bundle(component, locale)
9
- # source locale always uses source bundles
10
- return SgtnClient::Source.load_bundle(component) if SgtnClient::LocaleUtil.is_source_locale(locale)
5
+ autoload :StringUtil, 'sgtn-client/util/string-util'
6
+ autoload :LocaleUtil, 'sgtn-client/util/locale-util'
11
7
 
12
- translation_bundle_thread = Thread.new { super(component, locale) }
13
- old_source_bundle = super(component, SgtnClient::LocaleUtil.get_source_locale)
14
- source_bundle = get_cs(component, SgtnClient::LocaleUtil.get_source_locale)
15
- translation_bundle = translation_bundle_thread.value
8
+ module TranslationLoader
9
+ autoload :CONSTS, 'sgtn-client/loader/consts'
16
10
 
17
- compare_source(translation_bundle, old_source_bundle, source_bundle)
18
- end
11
+ module SourceComparer
12
+ def load_bundle(component, locale)
13
+ SgtnClient.logger.debug "[#{__FILE__}][#{__callee__}] component=#{component}, locale=#{locale}"
14
+
15
+ # source locale and old source locale don't need comparison because they are bases of comparison
16
+ real_locale = cache_to_real_map[locale]
17
+ return super(component, real_locale) if real_locale
18
+
19
+ old_source_bundle_thread = Thread.new { load_bundle(component, CONSTS::OLD_SOURCE_LOCALE) }
20
+ source_bundle_thread = Thread.new { load_bundle(component, LocaleUtil.get_source_locale) }
21
+ translation_bundle = super(component, locale)
22
+
23
+ begin
24
+ old_source_bundle = old_source_bundle_thread.value
25
+ source_bundle = source_bundle_thread.value
26
+ rescue StandardError => e
27
+ SgtnClient.logger.error "[#{__FILE__}][#{__callee__}] failed to load soruce(or old source) bundle. component:#{component}. error: #{e}"
28
+ return translation_bundle
29
+ end
30
+
31
+ compare_source(translation_bundle, old_source_bundle, source_bundle)
32
+ end
33
+
34
+ private
35
+
36
+ def compare_source(translation_bundle, old_source_bundle, source_bundle)
37
+ if !translation_bundle.is_a?(Hash) || !source_bundle.is_a?(Hash) || !old_source_bundle.is_a?(Hash)
38
+ SgtnClient.logger.warn "can't do source comparison because some bundle data are wrong."
39
+ return translation_bundle
40
+ end
41
+
42
+ source_bundle.each do |key, value|
43
+ if old_source_bundle[key] != value || translation_bundle[key].nil?
44
+ translation_bundle[key] = StringUtil.new(value, LocaleUtil.get_source_locale)
45
+ end
46
+ end
47
+ translation_bundle
48
+ end
19
49
 
20
- private
21
-
22
- def compare_source(translation_bundle, old_source_bundle, source_bundle)
23
- return translation_bundle if translation_bundle.nil? || source_bundle.nil? || old_source_bundle.nil?
24
-
25
- old_source_messages = old_source_bundle['messages']
26
- translation_messages = translation_bundle['messages']
27
- translation_bundle['messages'] = new_translation_messages = {}
28
- source_bundle['messages'].each do |key, value|
29
- translation = translation_messages[key]
30
- new_translation_messages[key] = if old_source_messages[key] == value && !translation.nil?
31
- translation
32
- else
33
- SgtnClient::StringUtil.new(value, SgtnClient::LocaleUtil.get_source_locale)
34
- end
50
+ def cache_to_real_map
51
+ @cache_to_real_map ||= {
52
+ LocaleUtil.get_source_locale => CONSTS::REAL_SOURCE_LOCALE,
53
+ CONSTS::OLD_SOURCE_LOCALE => LocaleUtil.get_source_locale
54
+ }.freeze
55
+ end
35
56
  end
36
- translation_bundle
37
57
  end
38
58
  end
@@ -17,6 +17,7 @@ module SgtnClient
17
17
  autoload :ValidateUtil, "sgtn-client/util/validate-util"
18
18
  autoload :LocaleUtil, "sgtn-client/util/locale-util"
19
19
  autoload :FileUtil, "sgtn-client/util/file-util"
20
+ autoload :CacheUtil, "sgtn-client/util/cache-util"
20
21
 
21
22
  module Formatters
22
23
  autoload :PluralFormatter, "sgtn-client/formatters/plurals/plural_formatter"
@@ -8,13 +8,11 @@ module SgtnClient
8
8
 
9
9
  class CacheUtil
10
10
  def self.get_cache(cache_key)
11
- SgtnClient.logger.debug "[CacheUtil]get cache with key #{cache_key}"
12
11
  SgtnClient::Core::Cache.get(cache_key)
13
12
  end
14
13
 
15
14
  def self.clear_cache
16
15
  SgtnClient::Core::Cache.clear
17
- SgtnClient.logger.debug '[CacheUtil]clear cache'
18
16
  end
19
17
 
20
18
  def self.write_cache(cache_key, items)
@@ -24,7 +22,6 @@ module SgtnClient
24
22
  cache_expiry_period = SgtnClient::Config.configurations[env]['cache_expiry_period']
25
23
  # expired after 24 hours
26
24
  cache_expiry_period = 24 * 60 if cache_expiry_period.nil?
27
- SgtnClient.logger.debug "[CacheUtil]write cache with key #{cache_key}, cache_expiry_period #{cache_expiry_period}"
28
25
  SgtnClient::Core::Cache.put(cache_key, items, cache_expiry_period)
29
26
  end
30
27
 
@@ -3,23 +3,21 @@
3
3
  require 'set'
4
4
 
5
5
  module SgtnClient
6
- SUPPORTED_LOCALES = %w[en de es fr ko ja zh-Hans zh-Hant zh de-CH].freeze # TODO get this from service in online mode
7
-
8
- MAP_LOCALES = {
9
- 'zh-CN' => 'zh-Hans',
10
- 'zh-TW' => 'zh-Hant',
11
- 'zh-Hans-CN' => 'zh-Hans',
12
- 'zh-Hant-TW' => 'zh-Hant'
13
- }.freeze
14
-
15
6
  class LocaleUtil
7
+ MAP_LOCALES = {
8
+ 'zh-CN' => 'zh-Hans',
9
+ 'zh-TW' => 'zh-Hant',
10
+ 'zh-Hans-CN' => 'zh-Hans',
11
+ 'zh-Hant-TW' => 'zh-Hant'
12
+ }.freeze
13
+
16
14
  def self.get_best_locale(locale)
17
- return get_default_locale if locale.nil?
15
+ return get_fallback_locale if locale.nil?
18
16
 
19
17
  locale = locale.to_s
20
- return get_default_locale if locale.empty?
18
+ return get_fallback_locale if locale.empty?
21
19
 
22
- get_best_match(locale)
20
+ get_best_match(locale.gsub('_', '-'))
23
21
  end
24
22
 
25
23
  def self.is_source_locale(locale = nil)
@@ -27,11 +25,13 @@ module SgtnClient
27
25
  end
28
26
 
29
27
  def self.get_best_match(locale)
30
- locale = locale.gsub('_', '-')
31
- locale = SgtnClient::MAP_LOCALES[locale] if SgtnClient::MAP_LOCALES.key?(locale)
32
- return locale if SUPPORTED_LOCALES.include?(locale)
33
- return LocaleUtil.get_source_locale if locale.index('-').nil?
34
- get_best_match(locale.slice(0..(locale.rindex('-')-1)) )
28
+ locale = MAP_LOCALES[locale] || locale
29
+ return locale if Config.available_locales.include?(locale)
30
+
31
+ index = locale.rindex('-')
32
+ return get_fallback_locale if index.nil?
33
+
34
+ get_best_match(locale[0...index])
35
35
  end
36
36
 
37
37
  def self.get_source_locale
@@ -40,7 +40,11 @@ module SgtnClient
40
40
 
41
41
  def self.get_default_locale
42
42
  env = SgtnClient::Config.default_environment
43
- SgtnClient::Config.configurations[env]['default_language'] || 'en'
43
+ SgtnClient::Config.configurations[env]['default_language']
44
+ end
45
+
46
+ def self.get_fallback_locale
47
+ @fallback_locale ||= get_default_locale || get_source_locale || 'en'
44
48
  end
45
49
 
46
50
  private_class_method :get_best_match
@@ -14,12 +14,7 @@ module SgtnClient
14
14
  if mode != 'sandbox' && mode != 'live'
15
15
  messages = messages + "Configuration[mode] has to be 'sandbox' or 'live'!\n"
16
16
  end
17
-
18
- bundle_mode = SgtnClient::Config.configurations[env]["bundle_mode"]
19
- if bundle_mode != 'offline' && bundle_mode != 'online'
20
- messages = messages + "Configuration[bundle_mode] has to be 'offline' or 'online'!\n"
21
- end
22
-
17
+
23
18
  #version = SgtnClient::Config.configurations[env]["version"]
24
19
  #if version.is_a? Integer
25
20
  #messages = messages + "Configuration[version] has to be standard as '#.#.#, e.g '1.0.0'!\n"
@@ -0,0 +1,15 @@
1
+ # Copyright 2022 VMware, Inc.
2
+ # SPDX-License-Identifier: EPL-2.0
3
+
4
+ require_relative 'singleton-ruby'
5
+
6
+ module Sgtn # :nodoc:
7
+ # load configuration from a file
8
+ def self.load_config(*args)
9
+ SgtnClient.load(*args)
10
+ end
11
+
12
+ extend SgtnClient::Translation::Implementation
13
+
14
+ private_class_method :getString, :getString_p, :getString_f, :getStrings
15
+ end
@@ -4,3 +4,5 @@
4
4
  require "sgtn-client/sgtn-client"
5
5
  require 'sgtn-client/cldr/core_ext'
6
6
  require 'twitter_cldr'
7
+
8
+ require 'sgtn-client/exceptions.rb'