fastlane-plugin-wpmreleasetoolkit 3.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87b7167d8eeecefac13e31850447419c88d3e2ceca851a89782df561846f4b7b
4
- data.tar.gz: 72b18c319cfbac6d39ea939c33cd801f1e777b79996ee617ea0649c0c3bfb420
3
+ metadata.gz: 9e6b39530c08effa688004cbc0c5d8fa8f4fcf3ae5795cb028ca7b60d84f25e0
4
+ data.tar.gz: bccb8c68d90bbb681f2fc41e896f1af775bf141ccd28d83eb3aac1f7ba40e28a
5
5
  SHA512:
6
- metadata.gz: 96c46a423eb4eaf0868bc5a6cecb6558a99dcf9f5f7b788bdc4255a2380aea4ed8adbd92d81476feaad00fc394182c70257aa140e7281920348b3ba8878bafba
7
- data.tar.gz: 5329bbb8827fd617af2d43dd1c770e2cf9a23e6adce430bf658f2ee68fe11cda3ac1e5d52f76ced293c28f23df3a9315a882cce14ad7f7918c370ed1be07b2e0
6
+ metadata.gz: 2d5324c615ca08aba37682e1cd6d613e885245f75f1cea5b459c644f083e8cd43af6ba6e6f93932ee7ce8e2a6bb21ed75a12f655610784494955226e2119fb54
7
+ data.tar.gz: d2fbbaeb3d7b753b400d77f33f8bf863a8eafc0dccdab8c4e36886f32af1b7791ca02a9dee75ad9ff09b29659d7af2887c4840d029411f6bc71be098378b8410
@@ -10,6 +10,7 @@ module Fastlane
10
10
 
11
11
  locales.each do |glotpress_locale, lproj_name|
12
12
  # Download the export in the proper `.lproj` directory
13
+ UI.message "Downloading translations for '#{lproj_name}' from GlotPress (#{glotpress_locale}) [#{params[:filters]}]..."
13
14
  lproj_dir = File.join(download_dir, "#{lproj_name}.lproj")
14
15
  destination = File.join(lproj_dir, "#{params[:table_basename]}.strings")
15
16
  FileUtils.mkdir(lproj_dir) unless Dir.exist?(lproj_dir)
@@ -3,8 +3,14 @@ module Fastlane
3
3
  class IosExtractKeysFromStringsFilesAction < Action
4
4
  def self.run(params)
5
5
  source_parent_dir = params[:source_parent_dir]
6
- target_original_files = params[:target_original_files]
7
- keys_to_extract_per_target_file = keys_list_per_target_file(target_original_files)
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 = []
8
14
 
9
15
  # For each locale, extract the right translations from `<source_tablename>.strings` into each target `.strings` file
10
16
  Dir.glob('*.lproj', base: source_parent_dir).each do |lproj_dir_name|
@@ -12,21 +18,25 @@ module Fastlane
12
18
  translations = Fastlane::Helper::Ios::L10nHelper.read_strings_file_as_hash(path: source_strings_file)
13
19
 
14
20
  target_original_files.each do |target_original_file|
15
- target_strings_file = File.join(File.dirname(File.dirname(target_original_file)), lproj_dir_name, File.basename(target_original_file))
21
+ target_strings_file = replace_lproj_in_path(target_original_file, with_lproj: lproj_dir_name)
16
22
  next if target_strings_file == target_original_file # do not generate/overwrite the original locale itself
17
23
 
18
- keys_to_extract = keys_to_extract_per_target_file[target_original_file]
19
- UI.message("Extracting #{keys_to_extract.count} keys into #{target_strings_file}...")
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}...")
20
28
 
21
- extracted_translations = translations.slice(*keys_to_extract)
22
29
  FileUtils.mkdir_p(File.dirname(target_strings_file)) # Ensure path up to parent dir exists, create it if not.
23
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)
24
32
  rescue StandardError => e
25
33
  UI.user_error!("Error while writing extracted translations to `#{target_strings_file}`: #{e.message}")
26
34
  end
27
35
  rescue StandardError => e
28
36
  UI.user_error!("Error while reading the translations from source file `#{source_strings_file}`: #{e.message}")
29
37
  end
38
+
39
+ updated_files_list
30
40
  end
31
41
 
32
42
  # Pre-load the list of keys to extract for each target file.
@@ -43,6 +53,15 @@ module Fastlane
43
53
  UI.user_error!("Failed to read the keys to extract from originals file: #{e.message}")
44
54
  end
45
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
+
46
65
  #####################################################
47
66
  # @!group Documentation
48
67
  #####################################################
@@ -82,28 +101,27 @@ module Fastlane
82
101
  default_value: 'Localizable'),
83
102
  FastlaneCore::ConfigItem.new(key: :target_original_files,
84
103
  env_name: 'FL_IOS_EXTRACT_KEYS_FROM_STRINGS_FILES_TARGET_ORIGINAL_FILES',
85
- description: 'The path(s) to the `<base-locale>.lproj/<target-tablename>.strings` file(s) for which we want to extract the keys to. ' \
86
- + 'Each of those files should containing the original strings (typically `en` or `Base` locale) and will be used to determine which keys to extract from the `source_tablename`. ' \
87
- + 'For each of those, the path(s) in which the translations will be extracted will be the files with the same basename in each of the other `*.lproj` sibling folders',
88
- type: Array,
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,
89
109
  verify_block: proc do |values|
90
110
  UI.user_error!('`target_original_files` must contain at least one path to an original `.strings` file.') if values.empty?
91
- values.each do |v|
92
- UI.user_error!("Path `#{v}` (found in `target_original_files`) does not exist.") unless File.exist?(v)
93
- UI.user_error! "Expected `#{v}` (found in `target_original_files`) to be a path ending in a `*.lproj/*.strings`." unless File.extname(v) == '.strings' && File.extname(File.dirname(v)) == '.lproj'
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'
94
114
  end
95
115
  end),
96
116
  ]
97
117
  end
98
118
 
99
119
  def self.return_type
100
- # Describes what type of data is expected to be returned
101
- # see RETURN_TYPES in https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/action.rb
102
- nil
120
+ :array_of_strings
103
121
  end
104
122
 
105
123
  def self.return_value
106
- # Freeform textual description of the return value
124
+ 'The list of files which have been generated and written to disk by the action'
107
125
  end
108
126
 
109
127
  def self.authors
@@ -2,12 +2,18 @@ module Fastlane
2
2
  module Actions
3
3
  class IosMergeStringsFilesAction < Action
4
4
  def self.run(params)
5
- UI.message "Merging strings files: #{params[:paths].inspect}"
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])
6
8
 
7
- duplicates = Fastlane::Helper::Ios::L10nHelper.merge_strings(paths: params[:paths], output_path: params[:destination])
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])
8
13
  duplicates.each do |dup_key|
9
14
  UI.important "Duplicate key found while merging the `.strings` files: `#{dup_key}`"
10
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?
11
17
  duplicates
12
18
  end
13
19
 
@@ -21,12 +27,11 @@ module Fastlane
21
27
 
22
28
  def self.details
23
29
  <<~DETAILS
24
- Merge multiple `.strings` files into one.
30
+ Merge multiple `.strings` files into another one.
25
31
 
26
- Especially useful to prepare a single `.strings` file merging strings from both `Localizable.strings` from
27
- the app codetypically previously extracted from `ios_generate_strings_file_from_code` —
28
- and string files like `InfoPlist.strings` — which values may not be generated from the codebase but
29
- manually maintained by developers.
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`.
30
35
 
31
36
  The action only supports merging files which are in the OpenStep (`"key" = "value";`) text format (which is
32
37
  the most common format for `.strings` files, and the one generated by `genstrings`), but can handle the case
@@ -38,19 +43,18 @@ module Fastlane
38
43
  def self.available_options
39
44
  [
40
45
  FastlaneCore::ConfigItem.new(
41
- key: :paths,
42
- env_name: 'FL_IOS_MERGE_STRINGS_FILES_PATHS',
43
- description: 'The paths of all the `.strings` files to merge together',
44
- type: Array,
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,
45
50
  optional: false
46
51
  ),
47
52
  FastlaneCore::ConfigItem.new(
48
53
  key: :destination,
49
54
  env_name: 'FL_IOS_MERGE_STRINGS_FILES_DESTINATION',
50
- description: 'The path of the merged `.strings` file to generate. If nil, the merge will happen in-place in the first file in the `paths:` list',
55
+ description: 'The path of the `.strings` file to merge the other ones into',
51
56
  type: String,
52
- optional: true,
53
- default_value: nil
57
+ optional: false
54
58
  ),
55
59
  ]
56
60
  end
@@ -60,7 +64,7 @@ module Fastlane
60
64
  end
61
65
 
62
66
  def self.return_value
63
- 'The list of duplicate keys found while merging the various `.strings` files'
67
+ 'The list of duplicate keys (after prefix has been added to each) found while merging the various `.strings` files'
64
68
  end
65
69
 
66
70
  def self.authors
@@ -19,6 +19,8 @@ module Fastlane
19
19
  # - `nil` if the file does not exist or is neither of those format (e.g. not a `.strings` file at all)
20
20
  #
21
21
  def self.strings_file_type(path:)
22
+ return :text if File.empty?(path) # If completely empty file, consider it as a valid `.strings` files in textual format
23
+
22
24
  # Start by checking it seems like a valid property-list file (and not e.g. an image or plain text file)
23
25
  _, status = Open3.capture2('/usr/bin/plutil', '-lint', path)
24
26
  return nil unless status.success?
@@ -36,8 +38,8 @@ module Fastlane
36
38
 
37
39
  # Merge the content of multiple `.strings` files into a new `.strings` text file.
38
40
  #
39
- # @param [Array<String>] paths The paths of the `.strings` files to merge together
40
- # @param [String] into The path to the merged `.strings` file to generate as a result.
41
+ # @param [Hash<String, String>] paths The paths of the `.strings` files to merge together, associated with the prefix to prepend to each of their respective keys
42
+ # @param [String] output_path The path to the merged `.strings` file to generate as a result.
41
43
  # @return [Array<String>] List of duplicate keys found while validating the merge.
42
44
  #
43
45
  # @note For now, this method only supports merging `.strings` file in `:text` format
@@ -48,24 +50,35 @@ module Fastlane
48
50
  #
49
51
  # @raise [RuntimeError] If one of the paths provided is not in text format (but XML or binary instead), or if any of the files are missing.
50
52
  #
51
- def self.merge_strings(paths:, output_path: nil)
53
+ def self.merge_strings(paths:, output_path:)
52
54
  duplicates = []
53
55
  Tempfile.create('wpmrt-l10n-merge-', encoding: 'utf-8') do |tmp_file|
54
56
  all_keys_found = []
55
57
 
56
58
  tmp_file.write("/* Generated File. Do not edit. */\n\n")
57
- paths.each do |input_file|
59
+ paths.each do |input_file, prefix|
60
+ next if File.empty?(input_file) # Skip existing but totally empty files, to avoid adding useless `MARK:` comment for them
61
+
58
62
  fmt = strings_file_type(path: input_file)
59
63
  raise "The file `#{input_file}` does not exist or is of unknown format." if fmt.nil?
60
64
  raise "The file `#{input_file}` is in #{fmt} format but we currently only support merging `.strings` files in text format." unless fmt == :text
61
65
 
62
- string_keys = read_strings_file_as_hash(path: input_file).keys
66
+ string_keys = read_strings_file_as_hash(path: input_file).keys.map { |k| "#{prefix}#{k}" }
63
67
  duplicates += (string_keys & all_keys_found) # Find duplicates using Array intersection, and add those to duplicates list
64
68
  all_keys_found += string_keys
65
69
 
66
70
  tmp_file.write("/* MARK: - #{File.basename(input_file)} */\n\n")
67
71
  # Read line-by-line to reduce memory footprint during content copy; Be sure to guess file encoding using the Byte-Order-Mark.
68
- File.readlines(input_file, mode: 'rb:BOM|UTF-8').each { |line| tmp_file.write(line) }
72
+ File.readlines(input_file, mode: 'rb:BOM|UTF-8').each do |line|
73
+ unless prefix.nil? || prefix.empty?
74
+ # We need to ensure the line and RegExp are using the same encoding, so we transcode everything to UTF-8.
75
+ line.encode!(Encoding::UTF_8)
76
+ # The `/u` modifier on the RegExps is to make them UTF-8
77
+ line.gsub!(/^(\s*")/u, "\\1#{prefix}") # Lines starting with a quote are considered to be start of a key; add prefix right after the quote
78
+ line.gsub!(/^(\s*)([A-Z0-9_]+)(\s*=\s*")/ui, "\\1\"#{prefix}\\2\"\\3") # Lines starting with an identifier followed by a '=' are considered to be an unquoted key (typical in InfoPlist.strings files for example)
79
+ end
80
+ tmp_file.write(line)
81
+ end
69
82
  tmp_file.write("\n")
70
83
  end
71
84
  tmp_file.close # ensure we flush the content to disk
@@ -81,6 +94,8 @@ module Fastlane
81
94
  # @raise [RuntimeError] If the file is not a valid strings file or there was an error in parsing its content.
82
95
  #
83
96
  def self.read_strings_file_as_hash(path:)
97
+ return {} if File.empty?(path) # Return empty hash if completely empty file
98
+
84
99
  output, status = Open3.capture2e('/usr/bin/plutil', '-convert', 'json', '-o', '-', path)
85
100
  raise output unless status.success?
86
101
 
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Wpmreleasetoolkit
3
- VERSION = '3.1.0'
3
+ VERSION = '4.0.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-wpmreleasetoolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorenzo Mattei
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-01 00:00:00.000000000 Z
11
+ date: 2022-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diffy