ad_localize 5.0.0 → 6.0.0

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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +6 -3
  3. data/.rubocop.yml +5 -0
  4. data/.rubocop_todo.yml +319 -0
  5. data/CHANGELOG.md +24 -0
  6. data/Gemfile +1 -0
  7. data/Gemfile.lock +54 -48
  8. data/README.md +32 -10
  9. data/Rakefile +1 -0
  10. data/ad_localize.gemspec +10 -9
  11. data/bin/console +1 -0
  12. data/exe/ad_localize +2 -1
  13. data/lib/ad_localize/ad_logger.rb +5 -10
  14. data/lib/ad_localize/cli.rb +10 -3
  15. data/lib/ad_localize/entities/key.rb +3 -76
  16. data/lib/ad_localize/entities/locale_wording.rb +32 -52
  17. data/lib/ad_localize/entities/platform.rb +13 -0
  18. data/lib/ad_localize/entities/simple_wording.rb +6 -0
  19. data/lib/ad_localize/entities/wording_type.rb +11 -0
  20. data/lib/ad_localize/interactors/base_generate_files.rb +37 -0
  21. data/lib/ad_localize/interactors/download_spreadsheets.rb +20 -0
  22. data/lib/ad_localize/interactors/export_wording.rb +26 -20
  23. data/lib/ad_localize/interactors/generate_info_plist.rb +24 -0
  24. data/lib/ad_localize/interactors/generate_ios_files.rb +12 -0
  25. data/lib/ad_localize/interactors/generate_json.rb +20 -0
  26. data/lib/ad_localize/interactors/generate_localizable_strings.rb +24 -0
  27. data/lib/ad_localize/interactors/generate_localizable_strings_dict.rb +24 -0
  28. data/lib/ad_localize/interactors/generate_properties.rb +20 -0
  29. data/lib/ad_localize/interactors/generate_strings.rb +25 -0
  30. data/lib/ad_localize/interactors/generate_yaml.rb +20 -0
  31. data/lib/ad_localize/interactors/merge_wordings.rb +49 -18
  32. data/lib/ad_localize/interactors/parse_csv_files.rb +22 -0
  33. data/lib/ad_localize/interactors/process_export_request.rb +21 -0
  34. data/lib/ad_localize/mappers/locale_wording_to_hash.rb +45 -24
  35. data/lib/ad_localize/mappers/options_to_export_request.rb +14 -22
  36. data/lib/ad_localize/option_handler.rb +50 -27
  37. data/lib/ad_localize/parsers/csv_parser.rb +84 -0
  38. data/lib/ad_localize/parsers/key_parser.rb +62 -0
  39. data/lib/ad_localize/repositories/drive_repository.rb +53 -0
  40. data/lib/ad_localize/repositories/file_system_repository.rb +2 -1
  41. data/lib/ad_localize/requests/export_request.rb +95 -57
  42. data/lib/ad_localize/sanitizers/ios_sanitizer.rb +12 -0
  43. data/lib/ad_localize/{mappers/android_translation_mapper.rb → sanitizers/ios_to_android_sanitizer.rb} +8 -8
  44. data/lib/ad_localize/sanitizers/pass_through_sanitizer.rb +10 -0
  45. data/lib/ad_localize/serializers/info_plist_serializer.rb +9 -11
  46. data/lib/ad_localize/serializers/json_serializer.rb +3 -5
  47. data/lib/ad_localize/serializers/localizable_strings_serializer.rb +9 -15
  48. data/lib/ad_localize/serializers/localizable_stringsdict_serializer.rb +12 -25
  49. data/lib/ad_localize/serializers/properties_serializer.rb +9 -11
  50. data/lib/ad_localize/serializers/strings_serializer.rb +12 -21
  51. data/lib/ad_localize/serializers/templated_serializer.rb +51 -0
  52. data/lib/ad_localize/serializers/yaml_serializer.rb +4 -6
  53. data/lib/ad_localize/templates/android/strings.xml.erb +6 -6
  54. data/lib/ad_localize/templates/ios/Localizable.stringsdict.erb +14 -14
  55. data/lib/ad_localize/version.rb +2 -1
  56. data/lib/ad_localize/view_models/compound_wording_view_model.rb +2 -0
  57. data/lib/ad_localize/view_models/simple_wording_view_model.rb +2 -0
  58. data/lib/ad_localize.rb +33 -35
  59. metadata +70 -47
  60. data/lib/ad_localize/constant.rb +0 -6
  61. data/lib/ad_localize/entities/translation.rb +0 -32
  62. data/lib/ad_localize/entities/wording.rb +0 -24
  63. data/lib/ad_localize/interactors/execute_export_request.rb +0 -45
  64. data/lib/ad_localize/interactors/export_csv_files.rb +0 -20
  65. data/lib/ad_localize/interactors/export_g_spreadsheet.rb +0 -64
  66. data/lib/ad_localize/interactors/platforms/export_android_locale_wording.rb +0 -44
  67. data/lib/ad_localize/interactors/platforms/export_csv_locale_wording.rb +0 -22
  68. data/lib/ad_localize/interactors/platforms/export_ios_locale_wording.rb +0 -75
  69. data/lib/ad_localize/interactors/platforms/export_json_locale_wording.rb +0 -27
  70. data/lib/ad_localize/interactors/platforms/export_platform_factory.rb +0 -50
  71. data/lib/ad_localize/interactors/platforms/export_properties_locale_wording.rb +0 -32
  72. data/lib/ad_localize/interactors/platforms/export_yaml_locale_wording.rb +0 -27
  73. data/lib/ad_localize/mappers/csv_path_to_wording.rb +0 -73
  74. data/lib/ad_localize/mappers/ios_translation_mapper.rb +0 -12
  75. data/lib/ad_localize/mappers/translation_group_mapper.rb +0 -14
  76. data/lib/ad_localize/mappers/translation_mapper.rb +0 -30
  77. data/lib/ad_localize/mappers/value_range_to_wording.rb +0 -67
  78. data/lib/ad_localize/repositories/g_sheets_repository.rb +0 -44
  79. data/lib/ad_localize/requests/export_wording_options.rb +0 -24
  80. data/lib/ad_localize/requests/g_spreadsheet_options.rb +0 -47
  81. data/lib/ad_localize/requests/merge_policy.rb +0 -28
  82. data/lib/ad_localize/serializers/with_template.rb +0 -19
  83. data/lib/ad_localize/validators/key_validator.rb +0 -31
  84. data/lib/ad_localize/view_models/translation_group_view_model.rb +0 -19
  85. data/lib/ad_localize/view_models/translation_view_model.rb +0 -23
@@ -1,96 +1,134 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Requests
3
4
  class ExportRequest
4
- SUPPORTED_PLATFORMS = %w(ios android yml json properties csv).freeze
5
- DEFAULT_EXPORT_FOLDER = 'exports'.freeze
6
- CSV_CONTENT_TYPES = %w(text/csv text/plain application/csv).freeze
7
- EMPTY_CONTENT_TYPE = 'inode/x-empty'.freeze
8
-
9
- def initialize(**args)
10
- @locales = Array(args[:locales].presence)
11
- @platforms = args[:platforms].blank? ? SUPPORTED_PLATFORMS : Array(args[:platforms])
12
- @csv_paths = Array(args[:csv_paths])
13
- @g_spreadsheet_options = args[:g_spreadsheet_options]
14
- @verbose = args[:verbose].presence || false
15
- @non_empty_values = args[:non_empty_values].presence || false
16
- @output_path = Pathname.new(args[:output_path].presence || DEFAULT_EXPORT_FOLDER)
17
- if @csv_paths.size > 1 || @g_spreadsheet_options&.has_multiple_sheets?
18
- @merge_policy = MergePolicy.new(policy: args[:merge_policy].presence || MergePolicy::DEFAULT_POLICY)
19
- else
20
- @merge_policy = nil
21
- end
22
- end
5
+ DEFAULTS = {
6
+ locales: [],
7
+ bypass_empty_values: false,
8
+ csv_paths: [],
9
+ merge_policy: Interactors::MergeWordings::DEFAULT_POLICY,
10
+ output_path: Pathname.new('exports'),
11
+ spreadsheet_id: nil,
12
+ sheet_ids: %w[0],
13
+ export_all: false,
14
+ verbose: false,
15
+ platforms: Entities::Platform::SUPPORTED_PLATFORMS,
16
+ downloaded_csvs: []
17
+ }
18
+
19
+ attr_accessor :output_dir
23
20
 
24
21
  attr_reader(
25
22
  :locales,
26
- :platforms,
27
- :g_spreadsheet_options,
23
+ :bypass_empty_values,
24
+ :csv_paths,
25
+ :merge_policy,
28
26
  :output_path,
27
+ :platforms,
28
+ :spreadsheet_id,
29
+ :sheet_ids,
30
+ :export_all,
29
31
  :verbose,
30
- :non_empty_values,
31
- :merge_policy
32
+ :downloaded_csvs
32
33
  )
33
34
 
34
- attr_accessor(:csv_paths)
35
+ def initialize
36
+ @locales = DEFAULTS[:locales]
37
+ @bypass_empty_values = DEFAULTS[:bypass_empty_values]
38
+ @csv_paths = DEFAULTS[:csv_paths]
39
+ @merge_policy = DEFAULTS[:merge_policy]
40
+ @output_path = DEFAULTS[:output_path]
41
+ @platforms = DEFAULTS[:platforms]
42
+ @spreadsheet_id = DEFAULTS[:spreadsheet_id]
43
+ @sheet_ids = DEFAULTS[:sheet_ids]
44
+ @export_all = DEFAULTS[:export_all]
45
+ @verbose = DEFAULTS[:verbose]
46
+ @downloaded_csvs = DEFAULTS[:downloaded_csvs]
47
+ end
48
+
49
+ def locales=(value)
50
+ return unless value.is_a? Array
51
+
52
+ @locales = value.compact
53
+ end
54
+
55
+ def bypass_empty_values=(value)
56
+ @bypass_empty_values = [true, 'true'].include?(value)
57
+ end
58
+
59
+ def csv_paths=(value)
60
+ return unless value.is_a? Array
35
61
 
36
- def has_csv_files?
37
- !@csv_paths.blank? && @csv_paths.all? { |csv_path| File.exist?(csv_path) && is_csv?(path: csv_path) }
62
+ @csv_paths = value.compact.map { |path| Pathname.new(path) }
38
63
  end
39
64
 
40
- def has_empty_files?
41
- !@csv_paths.blank? && @csv_paths.all? { |csv_path| File.exist?(csv_path) && is_empty?(path: csv_path) }
65
+ def merge_policy=(value)
66
+ @merge_policy = value unless value.blank?
42
67
  end
43
68
 
44
- def has_g_spreadsheet_options?
45
- @g_spreadsheet_options.present?
69
+ def output_path=(value)
70
+ @output_path = Pathname.new(value) unless value.blank?
46
71
  end
47
72
 
48
- def multiple_platforms?
49
- @platforms.size > 1
73
+ def platforms=(value)
74
+ return unless value.is_a? Array
75
+
76
+ @platforms = value.compact & DEFAULTS[:platforms]
50
77
  end
51
78
 
52
- def valid?
53
- valid_platforms? && (valid_csv_options? || valid_g_spreadsheet_options?)
79
+ def spreadsheet_id=(value)
80
+ @spreadsheet_id = value unless value.blank?
81
+ end
82
+
83
+ def sheet_ids=(value)
84
+ return unless value.is_a? Array
85
+
86
+ @sheet_ids = value.compact
54
87
  end
55
88
 
56
- def verbose?
57
- verbose
89
+ def export_all=(value)
90
+ @export_all = [true, 'true'].include?(value)
58
91
  end
59
92
 
60
- def non_empty_values?
61
- non_empty_values
93
+ def verbose=(value)
94
+ @verbose = [true, 'true'].include?(value)
62
95
  end
63
96
 
64
- private
97
+ def downloaded_csvs=(value)
98
+ return unless value.is_a? Array
65
99
 
66
- def valid_csv_options?
67
- has_csv_files? && (@csv_paths.size == 1 || (@csv_paths.size > 1 && @merge_policy&.valid?))
100
+ @downloaded_csvs = value.compact
68
101
  end
69
102
 
70
- def valid_platforms?
71
- @platforms.size.positive? && (@platforms & SUPPORTED_PLATFORMS).size == @platforms.size
103
+ def has_sheets?
104
+ spreadsheet_id.present?
72
105
  end
73
106
 
74
- def is_csv?(path:)
75
- CSV_CONTENT_TYPES.include? content_type(path: path)
107
+ def has_csv_paths?
108
+ all_csv_paths.present?
76
109
  end
77
110
 
78
- def is_empty?(path:)
79
- content_type(path: path) == EMPTY_CONTENT_TYPE
111
+ def many_platforms?
112
+ platforms.size > 1
80
113
  end
81
114
 
82
- def content_type(path:)
83
- `file --brief --mime-type "#{path}"`.strip
115
+ def all_csv_paths
116
+ csv_paths + downloaded_csvs.map(&:path)
84
117
  end
85
118
 
86
- def valid_g_spreadsheet_options?
87
- return false if @g_spreadsheet_options.blank?
88
- if @g_spreadsheet_options.has_multiple_sheets?
89
- @g_spreadsheet_options.valid? && @merge_policy&.valid?
90
- else
91
- @g_spreadsheet_options.valid?
92
- end
119
+ def to_s
120
+ "locales: #{locales}, " \
121
+ "bypass_empty_values: #{bypass_empty_values}, " \
122
+ "csv_paths: #{csv_paths}, " \
123
+ "merge_policy: #{merge_policy}, " \
124
+ "output_path: #{output_path}, " \
125
+ "spreadsheet_id: #{spreadsheet_id}, " \
126
+ "sheet_ids: #{sheet_ids}, " \
127
+ "export_all: #{export_all}, " \
128
+ "verbose: #{verbose}, " \
129
+ "platforms: #{platforms}, " \
130
+ "downloaded_csvs: #{downloaded_csvs}"
93
131
  end
94
132
  end
95
133
  end
96
- end
134
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module AdLocalize
3
+ module Sanitizers
4
+ class IOSSanitizer
5
+ def sanitize(value:)
6
+ return if value.blank?
7
+
8
+ value.gsub(/(?<!\\)\"/, "\\\"")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
- module Mappers
3
- class AndroidTranslationMapper < TranslationMapper
4
- private
5
-
6
- def sanitize_value(value:)
3
+ module Sanitizers
4
+ class IOSToAndroidSanitizer
5
+ def sanitize(value:)
7
6
  return if value.blank?
7
+
8
8
  processedValue = value.gsub(/(?<!\\)'/, '&#39;') # match ' unless there is a \ before
9
9
  processedValue = processedValue.gsub(/(?<!\\)\"/, '&#34;') # match " unless there is a \ before
10
10
  processedValue = processedValue.gsub(">", '&gt;')
@@ -13,9 +13,9 @@ module AdLocalize
13
13
  processedValue = processedValue.gsub(/(%(\d+\$)?@)/, '%\2s') # should match values like %1$s and %s
14
14
  hasFormatting = hasFormatting || processedValue.match(/(%((\d+\$)?(\d+)?)i)/)
15
15
  processedValue = processedValue.gsub(/(%((\d+\$)?(\d+)?)i)/, '%\2d') # should match values like %i, %3$i, %01i, %1$02i
16
- # On Android, '%' must be escaped with a second '%' if and only if the string has at least one formatting pattern. In this specific case,
16
+ # On Android, '%' must be escaped with a second '%' if and only if the string has at least one formatting pattern. In this specific case,
17
17
  # a Java formatting method will be used which interprets every non escaped '%' as the start of a formatting pattern.
18
- if hasFormatting
18
+ if hasFormatting
19
19
  processedValue = processedValue.gsub(/%(?!((\d+\$)?(s|(\d+)?d)))/, '%%') # negative lookahead: identifies when user really wants to display a %
20
20
  else
21
21
  processedValue = processedValue.gsub(/%(?!((\d+\$)?(s|(\d+)?d)))/, '%') # negative lookahead: identifies when user really wants to display a %
@@ -27,4 +27,4 @@ module AdLocalize
27
27
  end
28
28
  end
29
29
  end
30
- end
30
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module AdLocalize
3
+ module Sanitizers
4
+ class PassThroughSanitizer
5
+ def sanitize(value:)
6
+ value
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,12 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
- class InfoPlistSerializer
4
- include WithTemplate
5
-
4
+ class InfoPlistSerializer < TemplatedSerializer
6
5
  INFO_PLIST_FILENAME = "InfoPlist.strings".freeze
7
6
 
8
7
  def initialize
9
- @translation_mapper = Mappers::IOSTranslationMapper.new
8
+ super(sanitizer: Sanitizers::IOSSanitizer.new)
10
9
  end
11
10
 
12
11
  private
@@ -15,13 +14,12 @@ module AdLocalize
15
14
  TEMPLATES_DIRECTORY + "/ios/#{INFO_PLIST_FILENAME}.erb"
16
15
  end
17
16
 
18
- def hash_binding(locale_wording:)
19
- { translations: map_translations(translations: locale_wording.info_plists) }
20
- end
21
-
22
- def map_translations(translations:)
23
- translations.map { |translation| @translation_mapper.map(translation: translation) }
17
+ def variable_binding(locale_wording:)
18
+ translations = locale_wording.info_plists.map do |_, translation|
19
+ map_simple_wording(translation: translation)
20
+ end
21
+ { translations: translations }
24
22
  end
25
23
  end
26
24
  end
27
- end
25
+ end
@@ -1,12 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
4
  class JSONSerializer
4
- def initialize
5
- @locale_wording_to_hash = Mappers::LocaleWordingToHash.new
6
- end
7
-
8
5
  def render(locale_wording:)
9
- @locale_wording_to_hash.map(locale_wording: locale_wording).to_json
6
+ content = Mappers::LocaleWordingToHash.new.map(locale_wording: locale_wording)
7
+ content.to_json
10
8
  end
11
9
  end
12
10
  end
@@ -1,31 +1,25 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
- class LocalizableStringsSerializer
4
- include WithTemplate
5
-
4
+ class LocalizableStringsSerializer < TemplatedSerializer
6
5
  LOCALIZABLE_STRINGS_FILENAME = "Localizable.strings".freeze
7
6
 
8
7
  def initialize
9
- @translation_mapper = Mappers::IOSTranslationMapper.new
8
+ super(sanitizer: Sanitizers::IOSSanitizer.new)
10
9
  end
11
10
 
12
- attr_accessor(
13
- :bypass_empty_values
14
- )
15
-
16
11
  private
17
12
 
18
13
  def template_path
19
14
  TEMPLATES_DIRECTORY + "/ios/#{LOCALIZABLE_STRINGS_FILENAME}.erb"
20
15
  end
21
16
 
22
- def hash_binding(locale_wording:)
23
- { translations: map_translations(translations: locale_wording.singulars) }
24
- end
25
-
26
- def map_translations(translations:)
27
- translations.select(&:has_value?).map { |translation| @translation_mapper.map(translation: translation) }
17
+ def variable_binding(locale_wording:)
18
+ translations = locale_wording.singulars.map do |_, translation|
19
+ map_simple_wording(translation: translation)
20
+ end
21
+ { translations: translations }
28
22
  end
29
23
  end
30
24
  end
31
- end
25
+ end
@@ -1,41 +1,28 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
- class LocalizableStringsdictSerializer
4
- include WithTemplate
5
-
4
+ class LocalizableStringsdictSerializer < TemplatedSerializer
6
5
  LOCALIZABLE_STRINGSDICT_FILENAME = "Localizable.stringsdict".freeze
7
6
 
8
7
  def initialize
9
- @translation_mapper = Mappers::IOSTranslationMapper.new
10
- @translation_group_mapper = Mappers::TranslationGroupMapper.new(translation_mapper: @translation_mapper)
8
+ super(sanitizer: Sanitizers::IOSSanitizer.new)
11
9
  end
12
10
 
13
- attr_accessor(
14
- :bypass_empty_values
15
- )
16
-
17
11
  private
18
12
 
19
13
  def template_path
20
14
  TEMPLATES_DIRECTORY + "/ios/#{LOCALIZABLE_STRINGSDICT_FILENAME}.erb"
21
15
  end
22
16
 
23
- def hash_binding(locale_wording:)
24
- {
25
- plurals: map_plurals(plurals: locale_wording.plurals),
26
- adaptives: map_adaptives(adaptives: locale_wording.adaptives)
27
- }
28
- end
29
-
30
- def map_plurals(plurals:)
31
- plurals.map { |label, translations| @translation_group_mapper.map(label: label, translations: translations.select(&:has_value?)) }
32
- .select(&:has_translations?)
33
- end
34
-
35
- def map_adaptives(adaptives:)
36
- adaptives.map { |label, translations| @translation_group_mapper.map(label: label, translations: translations.select(&:has_value?)) }
37
- .select(&:has_translations?)
17
+ def variable_binding(locale_wording:)
18
+ plurals = locale_wording.plurals.map do |label, translations|
19
+ map_compound_wording(label: label, translations: translations)
20
+ end
21
+ adaptives = locale_wording.adaptives.map do |label, translations|
22
+ map_compound_wording(label: label, translations: translations)
23
+ end
24
+ { plurals: plurals, adaptives: adaptives }
38
25
  end
39
26
  end
40
27
  end
41
- end
28
+ end
@@ -1,10 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
- class PropertiesSerializer
4
- include WithTemplate
5
-
4
+ class PropertiesSerializer < TemplatedSerializer
6
5
  def initialize
7
- @translation_mapper = Mappers::TranslationMapper.new
6
+ super(sanitizer: Sanitizers::PassThroughSanitizer.new)
8
7
  end
9
8
 
10
9
  private
@@ -13,13 +12,12 @@ module AdLocalize
13
12
  TEMPLATES_DIRECTORY + "/properties/template.properties.erb"
14
13
  end
15
14
 
16
- def hash_binding(locale_wording:)
17
- { translations: map_translations(translations: locale_wording.singulars) }
18
- end
19
-
20
- def map_translations(translations:)
21
- translations.map { |translation| @translation_mapper.map(translation: translation) }
15
+ def variable_binding(locale_wording:)
16
+ singulars = locale_wording.singulars.map do |_, translation|
17
+ map_simple_wording(translation: translation)
18
+ end
19
+ { translations: singulars }
22
20
  end
23
21
  end
24
22
  end
25
- end
23
+ end
@@ -1,11 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
- class StringsSerializer
4
- include WithTemplate
5
-
4
+ class StringsSerializer < TemplatedSerializer
6
5
  def initialize
7
- @translation_mapper = Mappers::AndroidTranslationMapper.new
8
- @translation_group_mapper = Mappers::TranslationGroupMapper.new(translation_mapper: @translation_mapper)
6
+ super(sanitizer: Sanitizers::IOSToAndroidSanitizer.new)
9
7
  end
10
8
 
11
9
  private
@@ -14,22 +12,15 @@ module AdLocalize
14
12
  TEMPLATES_DIRECTORY + "/android/strings.xml.erb"
15
13
  end
16
14
 
17
- def hash_binding(locale_wording:)
18
- {
19
- singulars: map_singulars(translations: locale_wording.singulars),
20
- plurals: map_plurals(plurals: locale_wording.plurals)
21
- }
22
- end
23
-
24
- def map_singulars(translations:)
25
- translations.select(&:has_value?).map { |translation| @translation_mapper.map(translation: translation) }
26
- end
27
-
28
- def map_plurals(plurals:)
29
- plurals
30
- .map { |label, translations| @translation_group_mapper.map(label: label, translations: translations.select(&:has_value?)) }
31
- .select(&:has_translations?)
15
+ def variable_binding(locale_wording:)
16
+ singulars = locale_wording.singulars.map do |_, translation|
17
+ map_simple_wording(translation: translation)
18
+ end
19
+ plurals = locale_wording.plurals.map do |label, translations|
20
+ map_compound_wording(label: label, translations: translations)
21
+ end
22
+ { singulars: singulars, plurals: plurals }
32
23
  end
33
24
  end
34
25
  end
35
- end
26
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ module AdLocalize
3
+ module Serializers
4
+ class TemplatedSerializer
5
+ def initialize(sanitizer:)
6
+ @sanitizer = sanitizer
7
+ end
8
+
9
+ def render(locale_wording:)
10
+ variable_binding = variable_binding(locale_wording: locale_wording)
11
+ return unless variable_binding
12
+
13
+ render_template(template_path: template_path, variable_binding: variable_binding)
14
+ end
15
+
16
+ protected
17
+
18
+ TEMPLATES_DIRECTORY = __dir__ + "/../templates"
19
+
20
+ def template_path
21
+ raise 'Override me!'
22
+ end
23
+
24
+ def variable_binding(locale_wording:)
25
+ raise 'override me!'
26
+ end
27
+
28
+ private
29
+
30
+ def map_simple_wording(translation:)
31
+ SimpleWordingViewModel.new(
32
+ label: translation.key.label,
33
+ value: @sanitizer.sanitize(value: translation.value),
34
+ comment: translation.comment,
35
+ variant_name: translation.key.variant_name
36
+ )
37
+ end
38
+
39
+ def map_compound_wording(label:, translations:)
40
+ variants = translations.map { |_, translation| map_simple_wording(translation: translation) }
41
+ CompoundWordingViewModel.new(label: label, variants: variants)
42
+ end
43
+
44
+ def render_template(template_path:, variable_binding:)
45
+ template = File.read(template_path)
46
+ renderer = ERB.new(template, trim_mode: '-')
47
+ renderer.result_with_hash(variable_binding)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,13 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
3
  module Serializers
3
4
  class YAMLSerializer
4
- def initialize
5
- @locale_wording_to_hash = Mappers::LocaleWordingToHash.new
6
- end
7
-
8
5
  def render(locale_wording:)
9
- @locale_wording_to_hash.map(locale_wording: locale_wording).to_yaml
6
+ content = Mappers::LocaleWordingToHash.new.map(locale_wording: locale_wording)
7
+ content.to_yaml
10
8
  end
11
9
  end
12
10
  end
13
- end
11
+ end
@@ -6,13 +6,13 @@
6
6
  <%- end -%>
7
7
  <string name="<%= single_translation.label %>"><%= single_translation.value %></string>
8
8
  <%- end -%>
9
- <%- plurals.each do |translation_group| -%>
10
- <plurals name="<%= translation_group.label %>">
11
- <%- translation_group.translation_view_models.each do |translation_view_model| -%>
12
- <%- if translation_view_model.comment -%>
13
- <!-- <%= translation_view_model.comment %> -->
9
+ <%- plurals.each do |plural_group| -%>
10
+ <plurals name="<%= plural_group.label %>">
11
+ <%- plural_group.variants.each do |variant| -%>
12
+ <%- if variant.comment -%>
13
+ <!-- <%= variant.comment %> -->
14
14
  <%- end -%>
15
- <item quantity="<%= translation_view_model.key %>"><%= translation_view_model.value %></item>
15
+ <item quantity="<%= variant.variant_name %>"><%= variant.value %></item>
16
16
  <%- end -%>
17
17
  </plurals>
18
18
  <%- end -%>
@@ -1,8 +1,8 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <plist>
3
3
  <dict>
4
- <%- plurals.each do |translation_group| -%>
5
- <key><%= translation_group.label %></key>
4
+ <%- plurals.each do |plural_group| -%>
5
+ <key><%= plural_group.label %></key>
6
6
  <dict>
7
7
  <key>NSStringLocalizedFormatKey</key>
8
8
  <string>%#@key@</string>
@@ -12,27 +12,27 @@
12
12
  <string>NSStringPluralRuleType</string>
13
13
  <key>NSStringFormatValueTypeKey</key>
14
14
  <string>d</string>
15
- <%- translation_group.translation_view_models.each do |translation_view_model| -%>
16
- <%- if translation_view_model.comment -%>
17
- <!-- <%= translation_view_model.comment %> -->
15
+ <%- plural_group.variants.each do |variant| -%>
16
+ <%- if variant.comment -%>
17
+ <!-- <%= variant.comment %> -->
18
18
  <%- end -%>
19
- <key><%= translation_view_model.key -%></key>
20
- <string><%= translation_view_model.value -%></string>
19
+ <key><%= variant.variant_name -%></key>
20
+ <string><%= variant.value -%></string>
21
21
  <%- end -%>
22
22
  </dict>
23
23
  </dict>
24
24
  <%- end -%>
25
- <%- adaptives.each do |translation_group| -%>
26
- <key><%= translation_group.label %></key>
25
+ <%- adaptives.each do |adaptive_group| -%>
26
+ <key><%= adaptive_group.label %></key>
27
27
  <dict>
28
28
  <key>NSStringVariableWidthRuleType</key>
29
29
  <dict>
30
- <%- translation_group.translation_view_models.each do |translation_view_model| -%>
31
- <%- if translation_view_model.comment -%>
32
- <!-- <%= translation_view_model.comment %> -->
30
+ <%- adaptive_group.variants.each do |variant| -%>
31
+ <%- if variant.comment -%>
32
+ <!-- <%= variant.comment %> -->
33
33
  <%- end -%>
34
- <key><%= translation_view_model.key %></key>
35
- <string><%= translation_view_model.value %></string>
34
+ <key><%= variant.variant_name %></key>
35
+ <string><%= variant.value %></string>
36
36
  <%- end -%>
37
37
  </dict>
38
38
  </dict>
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module AdLocalize
2
- VERSION = "5.0.0"
3
+ VERSION = "6.0.0"
3
4
  end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ CompoundWordingViewModel = Struct.new(:label, :variants, keyword_init: true)
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ SimpleWordingViewModel = Struct.new(:label, :value, :comment, :variant_name, keyword_init: true)