fastlane-plugin-wpmreleasetoolkit 3.1.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 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