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.
- checksums.yaml +4 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/an_localize_libs_action.rb +8 -3
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_trigger_build_action.rb +90 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/gp_downloadmetadata_action.rb +1 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_to_s3.rb +112 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_download_strings_files_from_glotpress.rb +114 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_extract_keys_from_strings_files.rb +136 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_generate_strings_file_from_code.rb +1 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_lint_localizations.rb +5 -5
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_localize_project.rb +2 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_merge_strings_files.rb +79 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_git_helper.rb +0 -20
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_localize_helper.rb +8 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/git_helper.rb +2 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_git_helper.rb +3 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_helper.rb +120 -170
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_linter_helper.rb +207 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/promo_screenshots_helper.rb +3 -3
- data/lib/fastlane/plugin/wpmreleasetoolkit/models/file_reference.rb +1 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +1 -1
- metadata +25 -39
- data/bin/drawText +0 -20
- data/ext/drawText/drawText/Assets/style.css +0 -1
- data/ext/drawText/drawText/CoreTextStack.swift +0 -113
- data/ext/drawText/drawText/Helpers/CommandLineHelpers.swift +0 -36
- data/ext/drawText/drawText/Helpers/Extensions.swift +0 -27
- data/ext/drawText/drawText/Helpers/FileSystemHelper.swift +0 -24
- data/ext/drawText/drawText/Stylesheet.swift +0 -48
- data/ext/drawText/drawText/TextImage.swift +0 -100
- data/ext/drawText/drawText/main.swift +0 -61
- data/ext/drawText/drawText Tests/DigitParsingTests.swift +0 -21
- data/ext/drawText/drawText Tests/ExtensionsTests.swift +0 -5
- data/ext/drawText/drawText Tests/Info.plist +0 -22
- data/ext/drawText/drawText Tests/StylesheetTests.swift +0 -31
- data/ext/drawText/drawText Tests/Test Cases/default-stylesheet.txt +0 -10
- data/ext/drawText/drawText Tests/Test Cases/external-styles-sample.css +0 -3
- data/ext/drawText/drawText Tests/Test Cases/external-styles-test.txt +0 -13
- data/ext/drawText/drawText Tests/Test Cases/large-text-block.txt +0 -1
- data/ext/drawText/drawText Tests/Test Cases/regular-text-block.txt +0 -2
- data/ext/drawText/drawText Tests/Test Cases/rtl-text-block.txt +0 -2
- data/ext/drawText/drawText Tests/Test Cases/text-size-adjustment-test.txt +0 -10
- data/ext/drawText/drawText Tests/TextImageTests.swift +0 -99
- data/ext/drawText/drawText Tests/drawText_Tests.swift +0 -14
- data/ext/drawText/drawText.xcodeproj/project.pbxproj +0 -508
- data/ext/drawText/drawText.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- data/ext/drawText/drawText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- data/ext/drawText/drawText.xcodeproj/xcshareddata/xcschemes/drawText Tests.xcscheme +0 -57
- data/ext/drawText/drawText.xcodeproj/xcshareddata/xcschemes/drawText.xcscheme +0 -109
- data/ext/drawText/extconf.rb +0 -36
- data/ext/drawText/makefile.example +0 -8
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_merge_translators_strings.rb +0 -106
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_metadata.rb +0 -52
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e6b39530c08effa688004cbc0c5d8fa8f4fcf3ae5795cb028ca7b60d84f25e0
|
4
|
+
data.tar.gz: bccb8c68d90bbb681f2fc41e896f1af775bf141ccd28d83eb3aac1f7ba40e28a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
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
|
data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_download_strings_files_from_glotpress.rb
ADDED
@@ -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
|
data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_generate_strings_file_from_code.rb
CHANGED
@@ -36,7 +36,7 @@ module Fastlane
|
|
36
36
|
#####################################################
|
37
37
|
|
38
38
|
def self.description
|
39
|
-
'Generate the
|
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/
|
21
|
-
helper = Fastlane::Helper::Ios::
|
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::
|
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::
|
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::
|
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
|
19
|
+
'Gathers the strings to localise. Deprecated'
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.details
|
23
|
-
'Gathers the
|
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
|