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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +6 -3
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +319 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +54 -48
- data/README.md +32 -10
- data/Rakefile +1 -0
- data/ad_localize.gemspec +10 -9
- data/bin/console +1 -0
- data/exe/ad_localize +2 -1
- data/lib/ad_localize/ad_logger.rb +5 -10
- data/lib/ad_localize/cli.rb +10 -3
- data/lib/ad_localize/entities/key.rb +3 -76
- data/lib/ad_localize/entities/locale_wording.rb +32 -52
- data/lib/ad_localize/entities/platform.rb +13 -0
- data/lib/ad_localize/entities/simple_wording.rb +6 -0
- data/lib/ad_localize/entities/wording_type.rb +11 -0
- data/lib/ad_localize/interactors/base_generate_files.rb +37 -0
- data/lib/ad_localize/interactors/download_spreadsheets.rb +20 -0
- data/lib/ad_localize/interactors/export_wording.rb +26 -20
- data/lib/ad_localize/interactors/generate_info_plist.rb +24 -0
- data/lib/ad_localize/interactors/generate_ios_files.rb +12 -0
- data/lib/ad_localize/interactors/generate_json.rb +20 -0
- data/lib/ad_localize/interactors/generate_localizable_strings.rb +24 -0
- data/lib/ad_localize/interactors/generate_localizable_strings_dict.rb +24 -0
- data/lib/ad_localize/interactors/generate_properties.rb +20 -0
- data/lib/ad_localize/interactors/generate_strings.rb +25 -0
- data/lib/ad_localize/interactors/generate_yaml.rb +20 -0
- data/lib/ad_localize/interactors/merge_wordings.rb +49 -18
- data/lib/ad_localize/interactors/parse_csv_files.rb +22 -0
- data/lib/ad_localize/interactors/process_export_request.rb +21 -0
- data/lib/ad_localize/mappers/locale_wording_to_hash.rb +45 -24
- data/lib/ad_localize/mappers/options_to_export_request.rb +14 -22
- data/lib/ad_localize/option_handler.rb +50 -27
- data/lib/ad_localize/parsers/csv_parser.rb +84 -0
- data/lib/ad_localize/parsers/key_parser.rb +62 -0
- data/lib/ad_localize/repositories/drive_repository.rb +53 -0
- data/lib/ad_localize/repositories/file_system_repository.rb +2 -1
- data/lib/ad_localize/requests/export_request.rb +95 -57
- data/lib/ad_localize/sanitizers/ios_sanitizer.rb +12 -0
- data/lib/ad_localize/{mappers/android_translation_mapper.rb → sanitizers/ios_to_android_sanitizer.rb} +8 -8
- data/lib/ad_localize/sanitizers/pass_through_sanitizer.rb +10 -0
- data/lib/ad_localize/serializers/info_plist_serializer.rb +9 -11
- data/lib/ad_localize/serializers/json_serializer.rb +3 -5
- data/lib/ad_localize/serializers/localizable_strings_serializer.rb +9 -15
- data/lib/ad_localize/serializers/localizable_stringsdict_serializer.rb +12 -25
- data/lib/ad_localize/serializers/properties_serializer.rb +9 -11
- data/lib/ad_localize/serializers/strings_serializer.rb +12 -21
- data/lib/ad_localize/serializers/templated_serializer.rb +51 -0
- data/lib/ad_localize/serializers/yaml_serializer.rb +4 -6
- data/lib/ad_localize/templates/android/strings.xml.erb +6 -6
- data/lib/ad_localize/templates/ios/Localizable.stringsdict.erb +14 -14
- data/lib/ad_localize/version.rb +2 -1
- data/lib/ad_localize/view_models/compound_wording_view_model.rb +2 -0
- data/lib/ad_localize/view_models/simple_wording_view_model.rb +2 -0
- data/lib/ad_localize.rb +33 -35
- metadata +70 -47
- data/lib/ad_localize/constant.rb +0 -6
- data/lib/ad_localize/entities/translation.rb +0 -32
- data/lib/ad_localize/entities/wording.rb +0 -24
- data/lib/ad_localize/interactors/execute_export_request.rb +0 -45
- data/lib/ad_localize/interactors/export_csv_files.rb +0 -20
- data/lib/ad_localize/interactors/export_g_spreadsheet.rb +0 -64
- data/lib/ad_localize/interactors/platforms/export_android_locale_wording.rb +0 -44
- data/lib/ad_localize/interactors/platforms/export_csv_locale_wording.rb +0 -22
- data/lib/ad_localize/interactors/platforms/export_ios_locale_wording.rb +0 -75
- data/lib/ad_localize/interactors/platforms/export_json_locale_wording.rb +0 -27
- data/lib/ad_localize/interactors/platforms/export_platform_factory.rb +0 -50
- data/lib/ad_localize/interactors/platforms/export_properties_locale_wording.rb +0 -32
- data/lib/ad_localize/interactors/platforms/export_yaml_locale_wording.rb +0 -27
- data/lib/ad_localize/mappers/csv_path_to_wording.rb +0 -73
- data/lib/ad_localize/mappers/ios_translation_mapper.rb +0 -12
- data/lib/ad_localize/mappers/translation_group_mapper.rb +0 -14
- data/lib/ad_localize/mappers/translation_mapper.rb +0 -30
- data/lib/ad_localize/mappers/value_range_to_wording.rb +0 -67
- data/lib/ad_localize/repositories/g_sheets_repository.rb +0 -44
- data/lib/ad_localize/requests/export_wording_options.rb +0 -24
- data/lib/ad_localize/requests/g_spreadsheet_options.rb +0 -47
- data/lib/ad_localize/requests/merge_policy.rb +0 -28
- data/lib/ad_localize/serializers/with_template.rb +0 -19
- data/lib/ad_localize/validators/key_validator.rb +0 -31
- data/lib/ad_localize/view_models/translation_group_view_model.rb +0 -19
- data/lib/ad_localize/view_models/translation_view_model.rb +0 -23
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module AdLocalize
|
|
2
3
|
module Interactors
|
|
3
4
|
class MergeWordings
|
|
4
5
|
REPLACE_MERGE_POLICY = 'replace'.freeze
|
|
5
6
|
KEEP_MERGE_POLICY = 'keep'.freeze
|
|
6
7
|
MERGE_POLICIES = [KEEP_MERGE_POLICY, REPLACE_MERGE_POLICY]
|
|
7
|
-
|
|
8
|
-
def self.valid_policy?(policy:)
|
|
9
|
-
MERGE_POLICIES.include?(policy)
|
|
10
|
-
end
|
|
8
|
+
DEFAULT_POLICY = KEEP_MERGE_POLICY
|
|
11
9
|
|
|
12
10
|
def call(wordings:, merge_policy:)
|
|
13
11
|
if wordings.size == 1
|
|
@@ -20,24 +18,57 @@ module AdLocalize
|
|
|
20
18
|
|
|
21
19
|
private
|
|
22
20
|
|
|
21
|
+
def merge_simple_wordings(reference_list:, new_list:, merge_policy:)
|
|
22
|
+
new_list.each do |label, new_translation|
|
|
23
|
+
translation_reference = reference_list[label]
|
|
24
|
+
if translation_reference.nil?
|
|
25
|
+
reference_list[label] = new_translation
|
|
26
|
+
elsif merge_policy == REPLACE_MERGE_POLICY
|
|
27
|
+
LOGGER.warn("[MERGE] #{label} value changed from #{translation_reference.value} to #{new_translation.value}")
|
|
28
|
+
reference_list[label].value = new_translation.value
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def merge_compound_wordings(reference_hash:, new_hash:, merge_policy:)
|
|
34
|
+
new_hash.each do |new_label, new_list|
|
|
35
|
+
if reference_hash[new_label].nil?
|
|
36
|
+
reference_hash[new_label] = new_list
|
|
37
|
+
elsif merge_policy == REPLACE_MERGE_POLICY
|
|
38
|
+
merge_simple_wordings(reference_list: reference_hash[new_label], new_list: new_list, merge_policy: merge_policy)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
23
43
|
def merge_many(wordings:, merge_policy:)
|
|
24
|
-
|
|
44
|
+
wording_reference = wordings.first
|
|
25
45
|
wordings[1..-1].each do |wording|
|
|
26
|
-
wording.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
wording.each do |locale, locale_wording|
|
|
47
|
+
reference = wording_reference[locale]
|
|
48
|
+
merge_simple_wordings(
|
|
49
|
+
reference_list: reference.singulars,
|
|
50
|
+
new_list: locale_wording.singulars,
|
|
51
|
+
merge_policy: merge_policy
|
|
52
|
+
)
|
|
53
|
+
merge_simple_wordings(
|
|
54
|
+
reference_list: reference.info_plists,
|
|
55
|
+
new_list: locale_wording.info_plists,
|
|
56
|
+
merge_policy: merge_policy
|
|
57
|
+
)
|
|
58
|
+
merge_compound_wordings(
|
|
59
|
+
reference_hash: reference.plurals,
|
|
60
|
+
new_hash: locale_wording.plurals,
|
|
61
|
+
merge_policy: merge_policy
|
|
62
|
+
)
|
|
63
|
+
merge_compound_wordings(
|
|
64
|
+
reference_hash: reference.adaptives,
|
|
65
|
+
new_hash: locale_wording.adaptives,
|
|
66
|
+
merge_policy: merge_policy
|
|
67
|
+
)
|
|
37
68
|
end
|
|
38
69
|
end
|
|
39
|
-
|
|
70
|
+
wording_reference
|
|
40
71
|
end
|
|
41
72
|
end
|
|
42
73
|
end
|
|
43
|
-
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AdLocalize
|
|
3
|
+
module Interactors
|
|
4
|
+
class ParseCSVFiles
|
|
5
|
+
def initialize
|
|
6
|
+
@csv_parser = Parsers::CSVParser.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(export_request:)
|
|
10
|
+
csv_paths = export_request.all_csv_paths
|
|
11
|
+
LOGGER.debug("Will parse #{csv_paths.size} csv files")
|
|
12
|
+
wordings = csv_paths.filter_map do |csv_path|
|
|
13
|
+
@csv_parser.call(csv_path: csv_path, export_request: export_request)
|
|
14
|
+
end
|
|
15
|
+
LOGGER.debug("#{wordings.size} wording contents detected")
|
|
16
|
+
return if wordings.blank?
|
|
17
|
+
|
|
18
|
+
MergeWordings.new.call(wordings: wordings, merge_policy: export_request.merge_policy)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AdLocalize
|
|
3
|
+
module Interactors
|
|
4
|
+
class ProcessExportRequest
|
|
5
|
+
def call(export_request:)
|
|
6
|
+
export_request.verbose ? LOGGER.debug! : LOGGER.info!
|
|
7
|
+
LOGGER.debug(export_request)
|
|
8
|
+
LOGGER.debug("Verify if there are CSV to process")
|
|
9
|
+
return unless export_request.has_csv_paths?
|
|
10
|
+
|
|
11
|
+
LOGGER.debug("Parse CSV files")
|
|
12
|
+
wording = ParseCSVFiles.new.call(export_request: export_request)
|
|
13
|
+
return unless wording
|
|
14
|
+
|
|
15
|
+
LOGGER.debug("Export wording")
|
|
16
|
+
ExportWording.new.call(export_request: export_request, wording: wording)
|
|
17
|
+
LOGGER.debug("End of export request execution")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,33 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module AdLocalize
|
|
2
3
|
module Mappers
|
|
3
4
|
class LocaleWordingToHash
|
|
4
5
|
def map(locale_wording:)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
singulars_hash = map_singulars(simple_wordings: locale_wording.singulars.values)
|
|
7
|
+
plural_hash = map_plurals(coumpound_wordings: locale_wording.plurals)
|
|
8
|
+
locale_hash = singulars_hash.merge(plural_hash)
|
|
9
|
+
{ locale_wording.locale => locale_hash }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def inner_keys(label:)
|
|
15
|
+
label.split('.')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def map_singulars(simple_wordings:)
|
|
19
|
+
map_translations(translations: simple_wordings) do |inner_keys, translation|
|
|
20
|
+
dotted_key_to_hash(inner_keys, { inner_keys.pop => translation.value })
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def map_plurals(coumpound_wordings:)
|
|
25
|
+
result = {}
|
|
26
|
+
coumpound_wordings.each do |label, simple_wordings|
|
|
27
|
+
variants_hash = map_translations(translations: simple_wordings.values) do |keys, translation|
|
|
28
|
+
dotted_key_to_hash(keys, { translation.key.variant_name => translation.value })
|
|
27
29
|
end
|
|
30
|
+
result.deep_merge!(variants_hash)
|
|
31
|
+
end
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def map_translations(translations:)
|
|
36
|
+
translations.each_with_object({}) do |translation, hash|
|
|
37
|
+
keys = inner_keys(label: translation.key.label)
|
|
38
|
+
inner_keys_hash = yield(keys, translation)
|
|
39
|
+
hash.deep_merge!(inner_keys_hash)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def dotted_key_to_hash(array, h)
|
|
44
|
+
if array.size.zero?
|
|
45
|
+
h
|
|
46
|
+
elsif array.size == 1
|
|
47
|
+
{ array.first => h }
|
|
48
|
+
elsif array.size > 1
|
|
49
|
+
{ array.first => dotted_key_to_hash(array[1..-1], h) }
|
|
28
50
|
end
|
|
29
|
-
{ locale_wording.locale => result }
|
|
30
51
|
end
|
|
31
52
|
end
|
|
32
53
|
end
|
|
33
|
-
end
|
|
54
|
+
end
|
|
@@ -1,29 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module AdLocalize
|
|
2
3
|
module Mappers
|
|
3
4
|
class OptionsToExportRequest
|
|
4
5
|
def map(options:)
|
|
5
|
-
Requests::ExportRequest.new
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def map_g_spreadsheet_options(options:)
|
|
19
|
-
return unless options[:'drive-key']
|
|
20
|
-
Requests::GSpreadsheetOptions.new(
|
|
21
|
-
spreadsheet_id: options[:'drive-key'],
|
|
22
|
-
sheet_ids: options[:'sheets'],
|
|
23
|
-
export_all: options[:'export-all-sheets'],
|
|
24
|
-
service_account_config: ENV['GCLOUD_CLIENT_SECRET']
|
|
25
|
-
)
|
|
6
|
+
request = Requests::ExportRequest.new
|
|
7
|
+
request.locales = options[:locales]
|
|
8
|
+
request.bypass_empty_values = options[:'non-empty-values']
|
|
9
|
+
request.csv_paths = options[:csv_paths]
|
|
10
|
+
request.merge_policy = options[:'merge-policy']
|
|
11
|
+
request.output_path = options[:'target-dir']
|
|
12
|
+
request.platforms = options[:only]
|
|
13
|
+
request.spreadsheet_id = options[:'drive-key']
|
|
14
|
+
request.sheet_ids = options[:sheets]
|
|
15
|
+
request.export_all = options[:'export-all-sheets']
|
|
16
|
+
request.verbose = options[:debug]
|
|
17
|
+
request
|
|
26
18
|
end
|
|
27
19
|
end
|
|
28
20
|
end
|
|
29
|
-
end
|
|
21
|
+
end
|
|
@@ -1,39 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module AdLocalize
|
|
2
3
|
class OptionHandler
|
|
4
|
+
DEFAULT_OPTIONS = {
|
|
5
|
+
locales: Requests::ExportRequest::DEFAULTS[:locales],
|
|
6
|
+
:'non-empty-values' => Requests::ExportRequest::DEFAULTS[:bypass_empty_values],
|
|
7
|
+
csv_paths: Requests::ExportRequest::DEFAULTS[:csv_paths],
|
|
8
|
+
:'merge-policy' => Requests::ExportRequest::DEFAULTS[:merge_policy],
|
|
9
|
+
:'target-dir' => Requests::ExportRequest::DEFAULTS[:output_path],
|
|
10
|
+
:'drive-key' => Requests::ExportRequest::DEFAULTS[:spreadsheet_id],
|
|
11
|
+
sheets: Requests::ExportRequest::DEFAULTS[:sheet_ids],
|
|
12
|
+
:'export-all-sheets' => Requests::ExportRequest::DEFAULTS[:export_all],
|
|
13
|
+
debug: Requests::ExportRequest::DEFAULTS[:verbose],
|
|
14
|
+
only: Requests::ExportRequest::DEFAULTS[:platforms]
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
def self.parse!(options)
|
|
4
|
-
|
|
5
|
-
|
|
18
|
+
args = DEFAULT_OPTIONS
|
|
19
|
+
export_all_option = <<~DOC
|
|
20
|
+
Export all sheets from spreadsheet specified by --drive-key option.
|
|
21
|
+
\tBy default, generates one export directory per sheet (see -m|--merge-sheets option to merge them).
|
|
22
|
+
\tAn GCLOUD_CLIENT_SECRET environment variable containing the client_secret.json content is needed.
|
|
23
|
+
DOC
|
|
24
|
+
merge_policy_option = <<~DOC
|
|
25
|
+
Merge specified csv (or sheets from --export-all) instead of exporting each csv.
|
|
26
|
+
\treplace: if a key is already defined, replace its value.
|
|
27
|
+
\tkeep: if a key is already defined, keep the previous value.
|
|
28
|
+
DOC
|
|
29
|
+
platforms_option = <<~DOC
|
|
30
|
+
PLATFORMS is a comma separated list.
|
|
31
|
+
\tOnly generate localisation files for the specified platforms.
|
|
32
|
+
\tSupported platforms : #{Entities::Platform::SUPPORTED_PLATFORMS.to_sentence}
|
|
33
|
+
DOC
|
|
6
34
|
|
|
7
|
-
|
|
8
|
-
parser.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
\tAn GCLOUD_CLIENT_SECRET environment variable containing the client_secret.json content is needed.
|
|
13
|
-
DOC
|
|
14
|
-
)
|
|
15
|
-
parser.on("-h", "--help", "Prints help") do
|
|
35
|
+
OptionParser.new do |parser|
|
|
36
|
+
parser.banner = 'Usage: exe/ad_localize [options] file(s)'
|
|
37
|
+
parser.on("-d", "--debug", TrueClass, 'Run in debug mode')
|
|
38
|
+
parser.on("-e", "--export-all-sheets", TrueClass, export_all_option)
|
|
39
|
+
parser.on("-h", "--help", 'Prints help') do
|
|
16
40
|
puts parser
|
|
17
41
|
exit
|
|
18
42
|
end
|
|
19
|
-
parser.on("-k", "--drive-key SPREADSHEET_ID", String,
|
|
20
|
-
parser.on("-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
parser.on("-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
parser.on("-k", "--drive-key SPREADSHEET_ID", String, 'Use google drive spreadsheets')
|
|
44
|
+
parser.on("-l", "--locales LOCALES", Array,
|
|
45
|
+
'LOCALES is a comma separated list. Only generate localisation files for the specified locales')
|
|
46
|
+
parser.on("-m", "--merge-policy POLICY", Interactors::MergeWordings::MERGE_POLICIES, merge_policy_option)
|
|
47
|
+
parser.on("-o", "--only PLATFORMS", Array, platforms_option)
|
|
48
|
+
parser.on("-s", "--sheets SHEET_IDS", Array,
|
|
49
|
+
'SHEET_IDS is a comma separated list. Use a specific sheet id for Google Drive spreadsheets with several sheets')
|
|
50
|
+
parser.on("-t", "--target-dir PATH", String, 'Path to the target directory')
|
|
51
|
+
parser.on("-v", "--version", 'Prints current version') do
|
|
52
|
+
puts AdLocalize::VERSION
|
|
53
|
+
exit
|
|
54
|
+
end
|
|
55
|
+
parser.on("-x", "--non-empty-values", TrueClass, 'Do not export keys with empty values (iOS only)')
|
|
56
|
+
end.parse!(options, into: args)
|
|
32
57
|
|
|
33
|
-
args = {}
|
|
34
|
-
option_parser.parse!(options, into: args)
|
|
35
58
|
args[:csv_paths] = options
|
|
36
59
|
return args
|
|
37
60
|
end
|
|
38
61
|
end
|
|
39
|
-
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AdLocalize
|
|
3
|
+
module Parsers
|
|
4
|
+
class CSVParser
|
|
5
|
+
COMMENT_KEY_COLUMN_IDENTIFIER = 'comment'.freeze
|
|
6
|
+
CSV_WORDING_KEYS_COLUMN = 'key'.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(key_parser: nil)
|
|
9
|
+
@key_parser = key_parser.presence || KeyParser.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(csv_path:, export_request:)
|
|
13
|
+
locales = find_locales(csv_path: csv_path, export_request: export_request)
|
|
14
|
+
LOGGER.debug("#{csv_path} - locales : #{locales.to_sentence}")
|
|
15
|
+
return if locales.blank?
|
|
16
|
+
|
|
17
|
+
keys = find_keys(csv_path: csv_path)
|
|
18
|
+
wording = build_wording(
|
|
19
|
+
csv_path: csv_path,
|
|
20
|
+
locales: locales,
|
|
21
|
+
keys: keys,
|
|
22
|
+
export_request: export_request
|
|
23
|
+
)
|
|
24
|
+
wording
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def find_locales(csv_path:, export_request:)
|
|
30
|
+
csv = CSV.open(csv_path, headers: true, skip_blanks: true)
|
|
31
|
+
headers = csv.first.headers
|
|
32
|
+
locales = headers[headers.index(CSV_WORDING_KEYS_COLUMN).succ..-1].compact.reject do |header|
|
|
33
|
+
header.include?(COMMENT_KEY_COLUMN_IDENTIFIER)
|
|
34
|
+
end
|
|
35
|
+
export_request.locales.empty? ? locales : locales & export_request.locales
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def find_keys(csv_path:)
|
|
39
|
+
keys = {}
|
|
40
|
+
CSV.foreach(csv_path, headers: true, skip_blanks: true, skip_lines: /^#/) do |row|
|
|
41
|
+
next if row[CSV_WORDING_KEYS_COLUMN].blank?
|
|
42
|
+
|
|
43
|
+
raw_key = row[CSV_WORDING_KEYS_COLUMN].strip
|
|
44
|
+
key = @key_parser.call(raw_key: raw_key)
|
|
45
|
+
|
|
46
|
+
existing_key = keys.values.detect do |k|
|
|
47
|
+
k.id == key.id || (k.label == key.label && (k.variant_name.nil? || key.variant_name.nil?))
|
|
48
|
+
end
|
|
49
|
+
if existing_key
|
|
50
|
+
LOGGER.warn "A #{existing_key.type} value already exist for key '#{existing_key.label}'. Will skip new #{key.type} value. Remove duplicates."
|
|
51
|
+
else
|
|
52
|
+
keys[raw_key] = key
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
keys
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_wording(csv_path:, locales:, keys:, export_request:)
|
|
59
|
+
default_locale = locales.first
|
|
60
|
+
wording = Hash.new { |hash, key|
|
|
61
|
+
hash[key] = Entities::LocaleWording.new(locale: key, is_default: key == default_locale)
|
|
62
|
+
}
|
|
63
|
+
added_keys = Hash.new { |hash, key| hash[key] = false }
|
|
64
|
+
CSV.foreach(csv_path, headers: true, skip_blanks: true, skip_lines: /^#/) do |row|
|
|
65
|
+
next if row[CSV_WORDING_KEYS_COLUMN].blank?
|
|
66
|
+
|
|
67
|
+
raw_key = row[CSV_WORDING_KEYS_COLUMN].strip
|
|
68
|
+
key = keys[raw_key]
|
|
69
|
+
next if key.nil? || added_keys[raw_key]
|
|
70
|
+
|
|
71
|
+
locales.each do |locale|
|
|
72
|
+
value = row[locale]
|
|
73
|
+
next if export_request.bypass_empty_values && value.blank?
|
|
74
|
+
|
|
75
|
+
comment = row["#{COMMENT_KEY_COLUMN_IDENTIFIER} #{locale}"]
|
|
76
|
+
wording[locale].add_wording(key: key, value: value&.strip, comment: comment)
|
|
77
|
+
end
|
|
78
|
+
added_keys[raw_key] = true
|
|
79
|
+
end
|
|
80
|
+
wording
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AdLocalize
|
|
3
|
+
module Parsers
|
|
4
|
+
class KeyParser
|
|
5
|
+
PLURAL_KEY_REGEXP = /\#\#\{([A-Za-z]+)\}/.freeze
|
|
6
|
+
ADAPTIVE_KEY_REGEXP = /\#\#\{(\d+)\}/.freeze
|
|
7
|
+
# see https://developer.apple.com/documentation/bundleresources/information_property_list
|
|
8
|
+
INFO_PLIST_KEY_REGEXP = /(NS.+UsageDescription)|(CF.+Name)|NFCReaderUsageDescription/.freeze
|
|
9
|
+
|
|
10
|
+
def call(raw_key:)
|
|
11
|
+
type = compute_type(raw_key: raw_key)
|
|
12
|
+
label = compute_label(raw_key: raw_key, type: type)
|
|
13
|
+
variant_name = compute_variant_name(raw_key: raw_key, type: type)
|
|
14
|
+
Entities::Key.new(id: raw_key, label: label, type: type, variant_name: variant_name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def plural?(raw_key:)
|
|
18
|
+
!raw_key.match(PLURAL_KEY_REGEXP).nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def adaptive?(raw_key:)
|
|
22
|
+
!raw_key.match(ADAPTIVE_KEY_REGEXP).nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def info_plist?(raw_key:)
|
|
26
|
+
!raw_key.match(INFO_PLIST_KEY_REGEXP).nil?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def compute_type(raw_key:)
|
|
30
|
+
if plural?(raw_key: raw_key)
|
|
31
|
+
Entities::WordingType::PLURAL
|
|
32
|
+
elsif adaptive?(raw_key: raw_key)
|
|
33
|
+
Entities::WordingType::ADAPTIVE
|
|
34
|
+
elsif info_plist?(raw_key: raw_key)
|
|
35
|
+
Entities::WordingType::INFO_PLIST
|
|
36
|
+
else
|
|
37
|
+
Entities::WordingType::SINGULAR
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def compute_label(raw_key:, type:)
|
|
42
|
+
case type
|
|
43
|
+
when Entities::WordingType::PLURAL
|
|
44
|
+
raw_key.gsub(PLURAL_KEY_REGEXP, '')
|
|
45
|
+
when Entities::WordingType::ADAPTIVE
|
|
46
|
+
raw_key.gsub(ADAPTIVE_KEY_REGEXP, '')
|
|
47
|
+
else
|
|
48
|
+
raw_key
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def compute_variant_name(raw_key:, type:)
|
|
53
|
+
case type
|
|
54
|
+
when Entities::WordingType::PLURAL
|
|
55
|
+
raw_key.match(PLURAL_KEY_REGEXP)&.captures&.first
|
|
56
|
+
when Entities::WordingType::ADAPTIVE
|
|
57
|
+
raw_key.match(ADAPTIVE_KEY_REGEXP)&.captures&.first
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AdLocalize
|
|
3
|
+
module Repositories
|
|
4
|
+
class DriveRepository
|
|
5
|
+
def initialize
|
|
6
|
+
@drive_service = Google::Apis::DriveV3::DriveService.new
|
|
7
|
+
@sheet_service = Google::Apis::SheetsV4::SheetsService.new
|
|
8
|
+
if ENV['GOOGLE_APPLICATION_CREDENTIALS']
|
|
9
|
+
drive_scope = [Google::Apis::DriveV3::AUTH_DRIVE_READONLY]
|
|
10
|
+
sheet_scope = [Google::Apis::SheetsV4::AUTH_SPREADSHEETS_READONLY]
|
|
11
|
+
@drive_service.authorization = Google::Auth.get_application_default(drive_scope)
|
|
12
|
+
@sheet_service.authorization = Google::Auth.get_application_default(sheet_scope)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def download_sheets_by_id(spreadsheet_id:, sheet_ids:)
|
|
17
|
+
sheet_ids.filter_map do |sheet_id|
|
|
18
|
+
begin
|
|
19
|
+
url = export_url(spreadsheet_id: spreadsheet_id, sheet_id: sheet_id)
|
|
20
|
+
string = @drive_service.http(:get, url, options: { retries: 3, max_elapsed_time: 60 })
|
|
21
|
+
next unless string
|
|
22
|
+
|
|
23
|
+
tempfile = Tempfile.new
|
|
24
|
+
tempfile.write(string)
|
|
25
|
+
tempfile.rewind
|
|
26
|
+
tempfile
|
|
27
|
+
rescue => e
|
|
28
|
+
LOGGER.error("Cannot download sheet with id #{sheet_id}. Error: #{e.message}")
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def download_all_sheets(spreadsheet_id:)
|
|
35
|
+
begin
|
|
36
|
+
spreadsheet = @sheet_service.get_spreadsheet(spreadsheet_id)
|
|
37
|
+
sheet_ids = spreadsheet.sheets.map { |sheet| sheet.properties.sheet_id }
|
|
38
|
+
LOGGER.debug("#{sheet_ids.size} sheets in the spreadsheet")
|
|
39
|
+
download_sheets_by_id(spreadsheet_id: spreadsheet_id, sheet_ids: sheet_ids)
|
|
40
|
+
rescue => e
|
|
41
|
+
LOGGER.error("Cannot download sheets. Error: #{e.message}")
|
|
42
|
+
[]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def export_url(spreadsheet_id:, sheet_id:)
|
|
49
|
+
"https://docs.google.com/spreadsheets/d/#{spreadsheet_id}/export?format=csv&gid=#{sheet_id}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|