fastlane-plugin-wpmreleasetoolkit 2.3.0 → 4.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 (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