fastlane-plugin-wpmreleasetoolkit 2.3.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/an_localize_libs_action.rb +8 -3
  3. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_trigger_build_action.rb +90 -0
  4. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/gp_downloadmetadata_action.rb +1 -1
  5. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_to_s3.rb +112 -0
  6. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_download_strings_files_from_glotpress.rb +114 -0
  7. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_extract_keys_from_strings_files.rb +136 -0
  8. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_generate_strings_file_from_code.rb +1 -1
  9. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_lint_localizations.rb +5 -5
  10. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_localize_project.rb +2 -2
  11. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_merge_strings_files.rb +79 -0
  12. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_git_helper.rb +0 -20
  13. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_localize_helper.rb +8 -0
  14. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/git_helper.rb +2 -4
  15. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_git_helper.rb +3 -2
  16. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_helper.rb +120 -170
  17. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_linter_helper.rb +207 -0
  18. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/promo_screenshots_helper.rb +3 -3
  19. data/lib/fastlane/plugin/wpmreleasetoolkit/models/file_reference.rb +1 -4
  20. data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +1 -1
  21. metadata +25 -39
  22. data/bin/drawText +0 -20
  23. data/ext/drawText/drawText/Assets/style.css +0 -1
  24. data/ext/drawText/drawText/CoreTextStack.swift +0 -113
  25. data/ext/drawText/drawText/Helpers/CommandLineHelpers.swift +0 -36
  26. data/ext/drawText/drawText/Helpers/Extensions.swift +0 -27
  27. data/ext/drawText/drawText/Helpers/FileSystemHelper.swift +0 -24
  28. data/ext/drawText/drawText/Stylesheet.swift +0 -48
  29. data/ext/drawText/drawText/TextImage.swift +0 -100
  30. data/ext/drawText/drawText/main.swift +0 -61
  31. data/ext/drawText/drawText Tests/DigitParsingTests.swift +0 -21
  32. data/ext/drawText/drawText Tests/ExtensionsTests.swift +0 -5
  33. data/ext/drawText/drawText Tests/Info.plist +0 -22
  34. data/ext/drawText/drawText Tests/StylesheetTests.swift +0 -31
  35. data/ext/drawText/drawText Tests/Test Cases/default-stylesheet.txt +0 -10
  36. data/ext/drawText/drawText Tests/Test Cases/external-styles-sample.css +0 -3
  37. data/ext/drawText/drawText Tests/Test Cases/external-styles-test.txt +0 -13
  38. data/ext/drawText/drawText Tests/Test Cases/large-text-block.txt +0 -1
  39. data/ext/drawText/drawText Tests/Test Cases/regular-text-block.txt +0 -2
  40. data/ext/drawText/drawText Tests/Test Cases/rtl-text-block.txt +0 -2
  41. data/ext/drawText/drawText Tests/Test Cases/text-size-adjustment-test.txt +0 -10
  42. data/ext/drawText/drawText Tests/TextImageTests.swift +0 -99
  43. data/ext/drawText/drawText Tests/drawText_Tests.swift +0 -14
  44. data/ext/drawText/drawText.xcodeproj/project.pbxproj +0 -508
  45. data/ext/drawText/drawText.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  46. data/ext/drawText/drawText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  47. data/ext/drawText/drawText.xcodeproj/xcshareddata/xcschemes/drawText Tests.xcscheme +0 -57
  48. data/ext/drawText/drawText.xcodeproj/xcshareddata/xcschemes/drawText.xcscheme +0 -109
  49. data/ext/drawText/extconf.rb +0 -36
  50. data/ext/drawText/makefile.example +0 -8
  51. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_merge_translators_strings.rb +0 -106
  52. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_metadata.rb +0 -52
  53. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_merge_translators_strings.rb +0 -93
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9f3fcba792147f476a814f8f67f2620b5c6740497754eb033e2919e25dd3168
4
- data.tar.gz: 25077b056c019d26adad929b9fffbf942c23f22a60f66892905de241b624b2bb
3
+ metadata.gz: 9e6b39530c08effa688004cbc0c5d8fa8f4fcf3ae5795cb028ca7b60d84f25e0
4
+ data.tar.gz: bccb8c68d90bbb681f2fc41e896f1af775bf141ccd28d83eb3aac1f7ba40e28a
5
5
  SHA512:
6
- metadata.gz: d81b6b46eb930e0d15922c08eeea4314e9af06ef356b8dad207a6c76bec0793c1cf4e3c00507c16666170d3f6fe56960c9b3883ca567890e867fb1b50d064adf
7
- data.tar.gz: a92daf218f3ec9094a0d26edd0f6be4a6062c83609618ccdae26980afe9efd8b10ec6e4192f73290526106f93b6b352c94e55e3944053806ea0d06548ed155c7
6
+ metadata.gz: 2d5324c615ca08aba37682e1cd6d613e885245f75f1cea5b459c644f083e8cd43af6ba6e6f93932ee7ce8e2a6bb21ed75a12f655610784494955226e2119fb54
7
+ data.tar.gz: d2fbbaeb3d7b753b400d77f33f8bf863a8eafc0dccdab8c4e36886f32af1b7791ca02a9dee75ad9ff09b29659d7af2887c4840d029411f6bc71be098378b8410
@@ -36,12 +36,17 @@ module Fastlane
36
36
  FastlaneCore::ConfigItem.new(key: :app_strings_path,
37
37
  description: 'The path of the main strings file',
38
38
  optional: false,
39
- is_string: true),
39
+ type: String),
40
+ # The name of this parameter is a bit misleading due to legacy. In practice it's expected to be an Array of Hashes, each describing a library to merge.
41
+ # See `Fastlane::Helper::Android::LocalizeHelper.merge_lib`'s YARD doc for more details on the keys expected for each Hash.
40
42
  FastlaneCore::ConfigItem.new(key: :libs_strings_path,
41
43
  env_name: 'LOCALIZE_LIBS_STRINGS_PATH',
42
- description: 'The list of libs to merge',
44
+ description: 'The list of libs to merge. ' \
45
+ + 'Each item in the provided array must be a Hash with the keys `:library` (The library display name),' \
46
+ + '`:strings_path` (The path to the `strings.xml` file of the library) and ' \
47
+ + '`:exclusions` (Array of string keys to exclude from merging)',
43
48
  optional: false,
44
- is_string: false),
49
+ type: Array),
45
50
  ]
46
51
  end
47
52
 
@@ -0,0 +1,90 @@
1
+ module Fastlane
2
+ module Actions
3
+ class BuildkiteTriggerBuildAction < Action
4
+ def self.run(params)
5
+ require 'buildkit'
6
+
7
+ UI.message "Triggering build on branch #{params[:branch]}, commit #{params[:commit]}, using pipeline from #{params[:pipeline_file]}"
8
+
9
+ pipeline_name = {
10
+ PIPELINE: params[:pipeline_file]
11
+ }
12
+
13
+ client = Buildkit.new(token: params[:buildkite_token])
14
+ response = client.create_build(
15
+ params[:buildkite_organization],
16
+ params[:buildkite_pipeline],
17
+ {
18
+ branch: params[:branch],
19
+ commit: params[:commit],
20
+ env: params[:environment].merge(pipeline_name)
21
+ }
22
+ )
23
+
24
+ response.state == 'scheduled' ? UI.message('Done!') : UI.crash!("Failed to start job\nError: [#{response}]")
25
+ end
26
+
27
+ #####################################################
28
+ # @!group Documentation
29
+ #####################################################
30
+
31
+ def self.description
32
+ 'Triggers a job on Buildkite'
33
+ end
34
+
35
+ def self.available_options
36
+ [
37
+ FastlaneCore::ConfigItem.new(
38
+ key: :buildkite_token,
39
+ env_name: 'BUILDKITE_TOKEN',
40
+ description: 'Buildkite Personal Access Token',
41
+ type: String,
42
+ sensitive: true
43
+ ),
44
+ FastlaneCore::ConfigItem.new(
45
+ key: :buildkite_organization,
46
+ env_name: 'BUILDKITE_ORGANIZTION',
47
+ description: 'The Buildkite organization that contains your pipeline',
48
+ type: String
49
+ ),
50
+ FastlaneCore::ConfigItem.new(
51
+ key: :buildkite_pipeline,
52
+ env_name: 'BUILDKITE_PIPELINE',
53
+ description: %(The Buildkite pipeline you'd like to build),
54
+ type: String
55
+ ),
56
+ FastlaneCore::ConfigItem.new(
57
+ key: :branch,
58
+ description: 'The branch you want to build',
59
+ type: String
60
+ ),
61
+ FastlaneCore::ConfigItem.new(
62
+ key: :commit,
63
+ description: 'The commit hash you want to build',
64
+ type: String,
65
+ default_value: 'HEAD'
66
+ ),
67
+ FastlaneCore::ConfigItem.new(
68
+ key: :pipeline_file,
69
+ description: 'The name of the pipeline file in the project',
70
+ type: String
71
+ ),
72
+ FastlaneCore::ConfigItem.new(
73
+ key: :environment,
74
+ description: 'Any additional environment variables to provide to the job',
75
+ type: Hash,
76
+ default_value: {}
77
+ ),
78
+ ]
79
+ end
80
+
81
+ def self.authors
82
+ ['Automattic']
83
+ end
84
+
85
+ def self.is_supported?(platform)
86
+ true
87
+ end
88
+ end
89
+ end
90
+ end
@@ -58,7 +58,7 @@ module Fastlane
58
58
  is_string: false),
59
59
  FastlaneCore::ConfigItem.new(key: :locales,
60
60
  env_name: 'FL_DOWNLOAD_METADATA_LOCALES',
61
- description: 'The hash with the GLotPress locale and the project locale association',
61
+ description: 'The hash with the GlotPress locale and the project locale association',
62
62
  is_string: false),
63
63
  FastlaneCore::ConfigItem.new(key: :source_locale,
64
64
  env_name: 'FL_DOWNLOAD_METADATA_SOURCE_LOCALE',
@@ -0,0 +1,112 @@
1
+ require 'fastlane/action'
2
+ require 'digest/sha1'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ S3_UPLOADED_FILE_PATH = :S3_UPLOADED_FILE_PATH
8
+ end
9
+
10
+ class UploadToS3Action < Action
11
+ def self.run(params)
12
+ file_path = params[:file]
13
+ file_name = File.basename(file_path)
14
+
15
+ bucket = params[:bucket]
16
+ key = params[:key] || file_name
17
+
18
+ if params[:auto_prefix] == true
19
+ file_name_hash = Digest::SHA1.hexdigest(file_name)
20
+ key = [file_name_hash, key].join('/')
21
+ end
22
+
23
+ UI.user_error!("File already exists in S3 bucket #{bucket} at #{key}") if file_is_already_uploaded?(bucket, key)
24
+
25
+ UI.message("Uploading #{file_path} to: #{key}")
26
+
27
+ File.open(file_path, 'rb') do |file|
28
+ Aws::S3::Client.new().put_object(
29
+ body: file,
30
+ bucket: bucket,
31
+ key: key
32
+ )
33
+ rescue Aws::S3::Errors::ServiceError => e
34
+ UI.crash!("Unable to upload file to S3: #{e.message}")
35
+ end
36
+
37
+ UI.success('Upload Complete')
38
+
39
+ Actions.lane_context[SharedValues::S3_UPLOADED_FILE_PATH] = key
40
+
41
+ return key
42
+ end
43
+
44
+ def self.file_is_already_uploaded?(bucket, key)
45
+ response = Aws::S3::Client.new().head_object(
46
+ bucket: bucket,
47
+ key: key
48
+ )
49
+ return response[:content_length].positive?
50
+ rescue Aws::S3::Errors::NotFound
51
+ return false
52
+ end
53
+
54
+ def self.description
55
+ 'Uploads a given file to S3'
56
+ end
57
+
58
+ def self.authors
59
+ ['Automattic']
60
+ end
61
+
62
+ def self.return_value
63
+ 'Returns the object\'s derived S3 key'
64
+ end
65
+
66
+ def self.details
67
+ 'Uploads a file to S3, and makes a pre-signed URL available in the lane context'
68
+ end
69
+
70
+ def self.available_options
71
+ [
72
+ FastlaneCore::ConfigItem.new(
73
+ key: :bucket,
74
+ description: 'The bucket that will store the file',
75
+ optional: false,
76
+ type: String,
77
+ verify_block: proc { |bucket| UI.user_error!('You must provide a valid bucket name') if bucket.empty? }
78
+ ),
79
+ FastlaneCore::ConfigItem.new(
80
+ key: :key,
81
+ description: 'The path to the file within the bucket. If `nil`, will default to the `file\'s basename',
82
+ optional: true,
83
+ type: String,
84
+ verify_block: proc { |key|
85
+ next if key.is_a?(String) && !key.empty?
86
+
87
+ UI.user_error!('The provided key must not be empty. Use nil instead if you want to default to the file basename')
88
+ }
89
+ ),
90
+ FastlaneCore::ConfigItem.new(
91
+ key: :file,
92
+ description: 'The path to the local file on disk',
93
+ optional: false,
94
+ type: String,
95
+ verify_block: proc { |f| UI.user_error!("Path `#{f}` does not exist.") unless File.file?(f) }
96
+ ),
97
+ FastlaneCore::ConfigItem.new(
98
+ key: :auto_prefix,
99
+ description: 'Generate a derived prefix based on the filename that makes it harder to guess the URL of the uploaded object',
100
+ optional: true,
101
+ default_value: true,
102
+ type: Boolean
103
+ ),
104
+ ]
105
+ end
106
+
107
+ def self.is_supported?(platform)
108
+ true
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,114 @@
1
+ module Fastlane
2
+ module Actions
3
+ class IosDownloadStringsFilesFromGlotpressAction < Action
4
+ def self.run(params)
5
+ # TODO: Once we introduce the `Locale` POD via #296, check if the param is an array of locales and if so convert it to Hash{glotpress=>lproj}
6
+ locales = params[:locales]
7
+ download_dir = params[:download_dir]
8
+
9
+ UI.user_error!("The parent directory `#{download_dir}` (which contains all the `*.lproj` subdirectories) must already exist") unless Dir.exist?(download_dir)
10
+
11
+ locales.each do |glotpress_locale, lproj_name|
12
+ # Download the export in the proper `.lproj` directory
13
+ UI.message "Downloading translations for '#{lproj_name}' from GlotPress (#{glotpress_locale}) [#{params[:filters]}]..."
14
+ lproj_dir = File.join(download_dir, "#{lproj_name}.lproj")
15
+ destination = File.join(lproj_dir, "#{params[:table_basename]}.strings")
16
+ FileUtils.mkdir(lproj_dir) unless Dir.exist?(lproj_dir)
17
+
18
+ Fastlane::Helper::Ios::L10nHelper.download_glotpress_export_file(
19
+ project_url: params[:project_url],
20
+ locale: glotpress_locale,
21
+ filters: params[:filters],
22
+ destination: destination
23
+ )
24
+ # Do a quick check of the downloaded `.strings` file to ensure it looks valid
25
+ validate_strings_file(destination) unless params[:skip_file_validation]
26
+ end
27
+ end
28
+
29
+ # Validate that a `.strings` file downloaded from GlotPress seems valid and does not contain empty translations
30
+ def self.validate_strings_file(destination)
31
+ return unless File.exist?(destination) # If the file failed to download, don't try to validate an non-existing file. We'd already have a separate error for the download failure anyway.
32
+
33
+ translations = Fastlane::Helper::Ios::L10nHelper.read_strings_file_as_hash(path: destination)
34
+ empty_keys = translations.select { |_, value| value.nil? || value.empty? }.keys.sort
35
+ unless empty_keys.empty?
36
+ UI.error(
37
+ "Found empty translations in `#{destination}` for the following keys: #{empty_keys.inspect}.\n" \
38
+ + "This is likely a GlotPress bug, and will lead to copies replaced by empty text in the UI.\n" \
39
+ + 'Please report this to the GlotPress team, and fix the file locally before continuing.'
40
+ )
41
+ end
42
+ rescue StandardError => e
43
+ UI.error("Error while validating the file exported from GlotPress (`#{destination}`) - #{e.message.chomp}")
44
+ end
45
+
46
+ #####################################################
47
+ # @!group Documentation
48
+ #####################################################
49
+
50
+ def self.description
51
+ 'Downloads the `.strings` files from GlotPress for the various locales'
52
+ end
53
+
54
+ def self.details
55
+ <<~DETAILS
56
+ Downloads the `.strings` files from GlotPress for the various locales,
57
+ validates them, and saves them in the relevant `*.lproj` directories for each locale
58
+ DETAILS
59
+ end
60
+
61
+ def self.available_options
62
+ [
63
+ FastlaneCore::ConfigItem.new(key: :project_url,
64
+ env_name: 'FL_IOS_DOWNLOAD_STRINGS_FILES_FROM_GLOTPRESS_PROJECT_URL',
65
+ description: 'URL to the GlotPress project',
66
+ type: String),
67
+ FastlaneCore::ConfigItem.new(key: :locales,
68
+ env_name: 'FL_IOS_DOWNLOAD_STRINGS_FILES_FROM_GLOTPRESS_LOCALES',
69
+ description: 'The map of locales to download, each entry of the Hash corresponding to a { glotpress-locale-code => lproj-folder-basename } pair',
70
+ type: Hash), # TODO: also support an Array of `Locale` POD/struct type when we introduce it later (see #296)
71
+ FastlaneCore::ConfigItem.new(key: :download_dir,
72
+ env_name: 'FL_IOS_DOWNLOAD_STRINGS_FILES_FROM_GLOTPRESS_DOWNLOAD_DIR',
73
+ description: 'The parent directory containing all the `*.lproj` subdirectories in which the downloaded files will be saved',
74
+ type: String),
75
+ FastlaneCore::ConfigItem.new(key: :table_basename,
76
+ env_name: 'FL_IOS_DOWNLOAD_STRINGS_FILES_FROM_GLOTPRESS_TABLE_BASENAME',
77
+ description: 'The basename to save the `.strings` files under',
78
+ type: String,
79
+ optional: true,
80
+ default_value: 'Localizable'),
81
+ FastlaneCore::ConfigItem.new(key: :filters,
82
+ env_name: 'FL_IOS_DOWNLOAD_STRINGS_FILES_FROM_GLOTPRESS_FILTERS',
83
+ description: 'The GlotPress filters to use when requesting the translations export',
84
+ type: Hash,
85
+ optional: true,
86
+ default_value: { status: 'current' }),
87
+ FastlaneCore::ConfigItem.new(key: :skip_file_validation,
88
+ env_name: 'FL_IOS_DOWNLOAD_STRINGS_FILES_FROM_GLOTPRESS_SKIP_FILE_VALIDATION',
89
+ description: 'If true, skips the validation of `.strings` files after download',
90
+ type: Fastlane::Boolean,
91
+ optional: true,
92
+ default_value: false),
93
+ ]
94
+ end
95
+
96
+ def self.return_type
97
+ # Describes what type of data is expected to be returned
98
+ # see RETURN_TYPES in https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/action.rb
99
+ end
100
+
101
+ def self.return_value
102
+ # Textual description of what the return value is
103
+ end
104
+
105
+ def self.authors
106
+ ['Automattic']
107
+ end
108
+
109
+ def self.is_supported?(platform)
110
+ [:ios, :mac].include?(platform)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,136 @@
1
+ module Fastlane
2
+ module Actions
3
+ class IosExtractKeysFromStringsFilesAction < Action
4
+ def self.run(params)
5
+ source_parent_dir = params[:source_parent_dir]
6
+ target_original_files = params[:target_original_files].keys # Array [original-file-paths]
7
+ keys_to_extract_per_target_file = keys_list_per_target_file(target_original_files) # Hash { original-file-path => [keys] }
8
+ prefix_to_remove_per_target_file = params[:target_original_files] # Hash { original-file-path => prefix }
9
+
10
+ UI.message("Extracting keys from `#{source_parent_dir}/*.lproj/#{params[:source_tablename]}.strings` into:")
11
+ target_original_files.each { |f| UI.message(' - ' + replace_lproj_in_path(f, with_lproj: '*.lproj')) }
12
+
13
+ updated_files_list = []
14
+
15
+ # For each locale, extract the right translations from `<source_tablename>.strings` into each target `.strings` file
16
+ Dir.glob('*.lproj', base: source_parent_dir).each do |lproj_dir_name|
17
+ source_strings_file = File.join(source_parent_dir, lproj_dir_name, "#{params[:source_tablename]}.strings")
18
+ translations = Fastlane::Helper::Ios::L10nHelper.read_strings_file_as_hash(path: source_strings_file)
19
+
20
+ target_original_files.each do |target_original_file|
21
+ target_strings_file = replace_lproj_in_path(target_original_file, with_lproj: lproj_dir_name)
22
+ next if target_strings_file == target_original_file # do not generate/overwrite the original locale itself
23
+
24
+ keys_prefix = prefix_to_remove_per_target_file[target_original_file] || ''
25
+ keys_to_extract = keys_to_extract_per_target_file[target_original_file].map { |k| "#{keys_prefix}#{k}" }
26
+ extracted_translations = translations.slice(*keys_to_extract).transform_keys { |k| k.delete_prefix(keys_prefix) }
27
+ UI.verbose("Extracting #{extracted_translations.count} keys (out of #{keys_to_extract.count} expected) into #{target_strings_file}...")
28
+
29
+ FileUtils.mkdir_p(File.dirname(target_strings_file)) # Ensure path up to parent dir exists, create it if not.
30
+ Fastlane::Helper::Ios::L10nHelper.generate_strings_file_from_hash(translations: extracted_translations, output_path: target_strings_file)
31
+ updated_files_list.append(target_strings_file)
32
+ rescue StandardError => e
33
+ UI.user_error!("Error while writing extracted translations to `#{target_strings_file}`: #{e.message}")
34
+ end
35
+ rescue StandardError => e
36
+ UI.user_error!("Error while reading the translations from source file `#{source_strings_file}`: #{e.message}")
37
+ end
38
+
39
+ updated_files_list
40
+ end
41
+
42
+ # Pre-load the list of keys to extract for each target file.
43
+ #
44
+ # @param [Array<String>] original_files array of paths to the originals of target files
45
+ # @return [Hash<String, Array<String>>] The hash listing the keys to extract for each target file
46
+ #
47
+ def self.keys_list_per_target_file(original_files)
48
+ original_files.map do |original_file|
49
+ keys = Fastlane::Helper::Ios::L10nHelper.read_strings_file_as_hash(path: original_file).keys
50
+ [original_file, keys]
51
+ end.to_h
52
+ rescue StandardError => e
53
+ UI.user_error!("Failed to read the keys to extract from originals file: #{e.message}")
54
+ end
55
+
56
+ # Replaces the `*.lproj` component of the path to a `.strings` file with a different `.lproj` folder
57
+ #
58
+ # @param [String] path The path the the `.strings` file, assumed to be in a `.lproj` parent folder
59
+ # @param [String] with_lproj The new name of the `.lproj` parent folder to point to
60
+ #
61
+ def self.replace_lproj_in_path(path, with_lproj:)
62
+ File.join(File.dirname(File.dirname(path)), with_lproj, File.basename(path))
63
+ end
64
+
65
+ #####################################################
66
+ # @!group Documentation
67
+ #####################################################
68
+
69
+ def self.description
70
+ 'Extracts a subset of keys from a `.strings` file into separate `.strings` file(s)'
71
+ end
72
+
73
+ def self.details
74
+ <<~DETAILS
75
+ Extracts a subset of keys from a `.strings` file into separate `.strings` file(s), for each `*.lproj` subdirectory.
76
+
77
+ This is especially useful to extract, for each locale, the translations for files like `InfoPlist.strings` or
78
+ `<SomeIntentDefinitionFile>.strings` from the `Localizable.strings` file that we exported/downloaded back from GlotPress.
79
+
80
+ Since we typically merge all `*.strings` original files (e.g. `en.lproj/Localizable.strings` + `en.lproj/InfoPlist.strings` + …)
81
+ via `ios_merge_strings_file` before sending the originals to translations, we then need to extract the relevant keys and
82
+ translations back into the `*.lproj/InfoPlist.strings` after we pull those translations back from GlotPress
83
+ (`ios_download_strings_files_from_glotpress`). This is what this `ios_extract_keys_from_strings_files` action is for.
84
+ DETAILS
85
+ end
86
+
87
+ def self.available_options
88
+ [
89
+ FastlaneCore::ConfigItem.new(key: :source_parent_dir,
90
+ env_name: 'FL_IOS_EXTRACT_KEYS_FROM_STRINGS_FILES_SOURCE_PARENT_DIR',
91
+ description: 'The parent directory containing all the `*.lproj` subdirectories in which the source `.strings` files reside',
92
+ type: String,
93
+ verify_block: proc do |value|
94
+ UI.user_error!("`source_parent_dir` should be a path to an existing directory, but found `#{value}`.") unless File.directory?(value)
95
+ UI.user_error!("`source_parent_dir` should contain at least one `.lproj` subdirectory, but `#{value}` does not contain any.") if Dir.glob('*.lproj', base: value).empty?
96
+ end),
97
+ FastlaneCore::ConfigItem.new(key: :source_tablename,
98
+ env_name: 'FL_IOS_EXTRACT_KEYS_FROM_STRINGS_FILES_SOURCE_TABLENAME',
99
+ description: 'The basename of the `.strings` file (without the extension) to extract the keys and translations from for each locale',
100
+ type: String,
101
+ default_value: 'Localizable'),
102
+ FastlaneCore::ConfigItem.new(key: :target_original_files,
103
+ env_name: 'FL_IOS_EXTRACT_KEYS_FROM_STRINGS_FILES_TARGET_ORIGINAL_FILES',
104
+ description: 'The path(s) to the `<base-locale>.lproj/<target-tablename>.strings` file(s) for which we want to extract the keys to, and the prefix to remove from their keys. ' \
105
+ + 'Each key in the Hash should point to a file containing the original strings (typically `en` or `Base` locale), and will be used to determine which keys to extract from the `source_tablename`. ' \
106
+ + 'For each key, the associated value is an optional prefix to remove from the keys (which can be useful if you used a prefix during `ios_merge_strings_files` to avoid duplicates). Can be nil or empty if no prefix was used during merge for that file.' \
107
+ + 'Note: For each entry, the path(s) in which the translations will be extracted to will be the files with the same basename as the key in each of the other `*.lproj` sibling folders. ',
108
+ type: Hash,
109
+ verify_block: proc do |values|
110
+ UI.user_error!('`target_original_files` must contain at least one path to an original `.strings` file.') if values.empty?
111
+ values.each do |path, _|
112
+ UI.user_error!("Path `#{path}` (found in `target_original_files`) does not exist.") unless File.exist?(path)
113
+ UI.user_error! "Expected `#{path}` (found in `target_original_files`) to be a path ending in a `*.lproj/*.strings`." unless File.extname(path) == '.strings' && File.extname(File.dirname(path)) == '.lproj'
114
+ end
115
+ end),
116
+ ]
117
+ end
118
+
119
+ def self.return_type
120
+ :array_of_strings
121
+ end
122
+
123
+ def self.return_value
124
+ 'The list of files which have been generated and written to disk by the action'
125
+ end
126
+
127
+ def self.authors
128
+ ['Automattic']
129
+ end
130
+
131
+ def self.is_supported?(platform)
132
+ [:ios, :mac].include?(platform)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -36,7 +36,7 @@ module Fastlane
36
36
  #####################################################
37
37
 
38
38
  def self.description
39
- 'Generate the .strings files from your Objective-C and Swift code'
39
+ 'Generate the `.strings` files from your Objective-C and Swift code'
40
40
  end
41
41
 
42
42
  def self.details
@@ -17,8 +17,8 @@ module Fastlane
17
17
  def self.run_linter(params)
18
18
  UI.message 'Linting localizations for parameter placeholders consistency...'
19
19
 
20
- require_relative '../../helper/ios/ios_l10n_helper'
21
- helper = Fastlane::Helper::Ios::L10nHelper.new(
20
+ require_relative '../../helper/ios/ios_l10n_linter_helper'
21
+ helper = Fastlane::Helper::Ios::L10nLinterHelper.new(
22
22
  install_path: resolve_path(params[:install_path]),
23
23
  version: params[:version]
24
24
  )
@@ -92,7 +92,7 @@ module Fastlane
92
92
  description: 'The path where to install the SwiftGen tooling needed to run the linting process. If a relative path, should be relative to your repo_root',
93
93
  type: String,
94
94
  optional: true,
95
- default_value: "vendor/swiftgen/#{Fastlane::Helper::Ios::L10nHelper::SWIFTGEN_VERSION}"
95
+ default_value: "vendor/swiftgen/#{Fastlane::Helper::Ios::L10nLinterHelper::SWIFTGEN_VERSION}"
96
96
  ),
97
97
  FastlaneCore::ConfigItem.new(
98
98
  key: :version,
@@ -100,7 +100,7 @@ module Fastlane
100
100
  description: 'The version of SwiftGen to install and use for linting',
101
101
  type: String,
102
102
  optional: true,
103
- default_value: Fastlane::Helper::Ios::L10nHelper::SWIFTGEN_VERSION
103
+ default_value: Fastlane::Helper::Ios::L10nLinterHelper::SWIFTGEN_VERSION
104
104
  ),
105
105
  FastlaneCore::ConfigItem.new(
106
106
  key: :input_dir,
@@ -115,7 +115,7 @@ module Fastlane
115
115
  description: 'The language that should be used as the base language that every other language will be compared against',
116
116
  type: String,
117
117
  optional: true,
118
- default_value: Fastlane::Helper::Ios::L10nHelper::DEFAULT_BASE_LANG
118
+ default_value: Fastlane::Helper::Ios::L10nLinterHelper::DEFAULT_BASE_LANG
119
119
  ),
120
120
  FastlaneCore::ConfigItem.new(
121
121
  key: :only_langs,
@@ -16,11 +16,11 @@ module Fastlane
16
16
  #####################################################
17
17
 
18
18
  def self.description
19
- 'Gathers the string to localise'
19
+ 'Gathers the strings to localise. Deprecated'
20
20
  end
21
21
 
22
22
  def self.details
23
- 'Gathers the string to localise. Deprecated in favor of the new `ios_generate_strings_file_from_code`'
23
+ 'Gathers the strings to localise. Deprecated in favor of the new `ios_generate_strings_file_from_code`'
24
24
  end
25
25
 
26
26
  def self.category
@@ -0,0 +1,79 @@
1
+ module Fastlane
2
+ module Actions
3
+ class IosMergeStringsFilesAction < Action
4
+ def self.run(params)
5
+ destination = params[:destination]
6
+ # Include the destination as the first of the files (without key prefixes) to be included in the merged result
7
+ all_paths_list = { destination => nil }.merge(params[:paths_to_merge])
8
+
9
+ UI.message "Merging strings files #{all_paths_list.inspect}"
10
+
11
+ File.write(destination, '') unless File.exist?(destination) # Create empty destination file if it does not exist yet
12
+ duplicates = Fastlane::Helper::Ios::L10nHelper.merge_strings(paths: all_paths_list, output_path: params[:destination])
13
+ duplicates.each do |dup_key|
14
+ UI.important "Duplicate key found while merging the `.strings` files: `#{dup_key}`"
15
+ end
16
+ UI.important 'Tip: To avoid those key conflicts, you might want to consider providing different prefixes in the `Hash` you used for the `paths:` parameter.' unless duplicates.empty?
17
+ duplicates
18
+ end
19
+
20
+ #####################################################
21
+ # @!group Documentation
22
+ #####################################################
23
+
24
+ def self.description
25
+ 'Merge multiple `.strings` files into one'
26
+ end
27
+
28
+ def self.details
29
+ <<~DETAILS
30
+ Merge multiple `.strings` files into another one.
31
+
32
+ Especially useful to prepare a single `.strings` file merging string files like `InfoPlist.strings` — whose
33
+ content are typically manually maintained by developers — within the main `Localizable.strings` file — which
34
+ would have typically been previously generated from the codebase via `ios_generate_strings_file_from_code`.
35
+
36
+ The action only supports merging files which are in the OpenStep (`"key" = "value";`) text format (which is
37
+ the most common format for `.strings` files, and the one generated by `genstrings`), but can handle the case
38
+ of different files using different encodings (UTF8 vs UTF16) and is able to detect and report duplicates.
39
+ It does not handle `.strings` files in XML or binary-plist formats (which are valid but more rare)
40
+ DETAILS
41
+ end
42
+
43
+ def self.available_options
44
+ [
45
+ FastlaneCore::ConfigItem.new(
46
+ key: :paths_to_merge,
47
+ env_name: 'FL_IOS_MERGE_STRINGS_FILES_PATHS_TO_MERGE',
48
+ description: 'A hash of the paths of all the `.strings` files to merge into the `destination`, with the prefix to be used for their keys as associated value',
49
+ type: Hash,
50
+ optional: false
51
+ ),
52
+ FastlaneCore::ConfigItem.new(
53
+ key: :destination,
54
+ env_name: 'FL_IOS_MERGE_STRINGS_FILES_DESTINATION',
55
+ description: 'The path of the `.strings` file to merge the other ones into',
56
+ type: String,
57
+ optional: false
58
+ ),
59
+ ]
60
+ end
61
+
62
+ def self.return_type
63
+ :array_of_strings
64
+ end
65
+
66
+ def self.return_value
67
+ 'The list of duplicate keys (after prefix has been added to each) found while merging the various `.strings` files'
68
+ end
69
+
70
+ def self.authors
71
+ ['automattic']
72
+ end
73
+
74
+ def self.is_supported?(platform)
75
+ [:ios, :mac].include? platform
76
+ end
77
+ end
78
+ end
79
+ end