fastlane-plugin-wpmreleasetoolkit 2.0.0 → 3.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/android/android_betabuild_prechecks.rb +8 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_bump_version_release.rb +11 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_codefreeze_prechecks.rb +10 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/buildkite_trigger_build_action.rb +90 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/comment_on_pr.rb +89 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb +2 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/gp_downloadmetadata_action.rb +1 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_betabuild_prechecks.rb +8 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_bump_version_release.rb +10 -5
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_codefreeze_prechecks.rb +10 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_download_strings_files_from_glotpress.rb +113 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_generate_strings_file_from_code.rb +115 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_lint_localizations.rb +5 -5
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_localize_project.rb +6 -7
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_merge_strings_files.rb +75 -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 +3 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +48 -8
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/interactive_prompt_reminder.rb +93 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_git_helper.rb +3 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_helper.rb +108 -173
- 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 +26 -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
@@ -1,205 +1,140 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'open3'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'tempfile'
|
3
7
|
|
4
8
|
module Fastlane
|
5
9
|
module Helper
|
6
10
|
module Ios
|
7
11
|
class L10nHelper
|
8
|
-
|
9
|
-
DEFAULT_BASE_LANG = 'en'
|
10
|
-
CONFIG_FILE_NAME = 'swiftgen-stringtypes.yml'
|
11
|
-
|
12
|
-
attr_reader :install_path, :version
|
13
|
-
|
14
|
-
# @param [String] install_path The path to install SwiftGen to. Usually something like "$PROJECT_DIR/vendor/swiftgen/#{SWIFTGEN_VERSION}".
|
15
|
-
# It's recommended to provide an absolute path here rather than a relative one, to ensure it's not dependant on where the action is run from.
|
16
|
-
# @param [String] version The version of SwiftGen to use. This will be used both:
|
17
|
-
# - to check if the current version located in `install_path`, if it already exists, is the expected one
|
18
|
-
# - to know which version to download if there is not one installed in `install_path` yet
|
19
|
-
#
|
20
|
-
def initialize(install_path:, version: SWIFTGEN_VERSION)
|
21
|
-
@install_path = install_path
|
22
|
-
@version = version || SWIFTGEN_VERSION
|
23
|
-
end
|
24
|
-
|
25
|
-
# Check if SwiftGen is installed in the provided `install_path` and if so if the installed version matches the expected `version`
|
26
|
-
#
|
27
|
-
def check_swiftgen_installed
|
28
|
-
return false unless File.exist?(swiftgen_bin)
|
29
|
-
|
30
|
-
vers_string = `#{swiftgen_bin} --version`
|
31
|
-
# The SwiftGen version string has this format:
|
32
|
-
#
|
33
|
-
# SwiftGen v6.4.0 (Stencil v0.13.1, StencilSwiftKit v2.7.2, SwiftGenKit v6.4.0)
|
34
|
-
return vers_string.include?("SwiftGen v#{version}")
|
35
|
-
rescue
|
36
|
-
return false
|
37
|
-
end
|
38
|
-
|
39
|
-
# Download the ZIP of SwiftGen for the requested `version` and install it in the `install_path`
|
12
|
+
# Returns the type of a `.strings` file (XML, binary or ASCII)
|
40
13
|
#
|
41
|
-
# @
|
14
|
+
# @param [String] path The path to the `.strings` file to check
|
15
|
+
# @return [Symbol] The file format used by the `.strings` file. Can be one of:
|
16
|
+
# - `:text` for the ASCII-plist file format (containing typical `"key" = "value";` lines)
|
17
|
+
# - `:xml` for XML plist file format (can be used if machine-generated, especially since there's no official way/tool to generate the ASCII-plist file format as output)
|
18
|
+
# - `:binary` for binary plist file format (usually only true for `.strings` files converted by Xcode at compile time and included in the final `.app`/`.ipa`)
|
19
|
+
# - `nil` if the file does not exist or is neither of those format (e.g. not a `.strings` file at all)
|
42
20
|
#
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
21
|
+
def self.strings_file_type(path:)
|
22
|
+
# Start by checking it seems like a valid property-list file (and not e.g. an image or plain text file)
|
23
|
+
_, status = Open3.capture2('/usr/bin/plutil', '-lint', path)
|
24
|
+
return nil unless status.success?
|
25
|
+
|
26
|
+
# If it is a valid property-list file, determine the actual format used
|
27
|
+
format_desc, status = Open3.capture2('/usr/bin/file', path)
|
28
|
+
return nil unless status.success?
|
29
|
+
|
30
|
+
case format_desc
|
31
|
+
when /Apple binary property list/ then return :binary
|
32
|
+
when /XML/ then return :xml
|
33
|
+
when /text/ then return :text
|
54
34
|
end
|
55
35
|
end
|
56
36
|
|
57
|
-
#
|
37
|
+
# Merge the content of multiple `.strings` files into a new `.strings` text file.
|
58
38
|
#
|
59
|
-
# @param [String]
|
60
|
-
# @param [String]
|
61
|
-
# @return [
|
62
|
-
# and the values are the output of the `diff` showing these violations.
|
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
|
+
# @return [Array<String>] List of duplicate keys found while validating the merge.
|
63
42
|
#
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
##################
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
# Path to the swiftgen binary installed at install_path
|
74
|
-
def swiftgen_bin
|
75
|
-
"#{install_path}/bin/swiftgen"
|
76
|
-
end
|
77
|
-
|
78
|
-
# Name to use for the generated files / output files of SwiftGen for each locale. Those files will be generated in the temporary directory to then diff them.
|
79
|
-
def output_filename(lang)
|
80
|
-
"L10nParamsList.#{lang}.txt"
|
81
|
-
end
|
82
|
-
|
83
|
-
# The Stencil template that we want SwiftGen to use to generate the output.
|
84
|
-
# It iterates on every "table" (`.strings` file, in most cases there's only one, `Localizable.strings`),
|
85
|
-
# and for each, iterates on every entry found to print the key and the corresponding types parsed by SwiftGen from the placeholders found in that translation
|
86
|
-
def template_content
|
87
|
-
<<~TEMPLATE
|
88
|
-
{% macro recursiveBlock table item %}
|
89
|
-
{% for string in item.strings %}
|
90
|
-
"{{string.key}}" => [{{string.types|join:","}}]
|
91
|
-
{% endfor %}
|
92
|
-
{% for child in item.children %}
|
93
|
-
{% call recursiveBlock table child %}
|
94
|
-
{% endfor %}
|
95
|
-
{% endmacro %}
|
96
|
-
|
97
|
-
{% for table in tables %}
|
98
|
-
{% call recursiveBlock table.name table.levels %}
|
99
|
-
{% endfor %}
|
100
|
-
TEMPLATE
|
101
|
-
end
|
102
|
-
|
103
|
-
# Create the template file and the config file, in the temp dir, to be used by SwiftGen when parsing the input files.
|
43
|
+
# @note For now, this method only supports merging `.strings` file in `:text` format
|
44
|
+
# and basically concatenates the files (+ checking for duplicates in the process)
|
45
|
+
# @note The method is able to handle input files which are using different encodings,
|
46
|
+
# guessing the encoding of each input file using the BOM (and defaulting to UTF8).
|
47
|
+
# The generated file will always be in utf-8, by convention.
|
104
48
|
#
|
105
|
-
# @
|
49
|
+
# @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.
|
106
50
|
#
|
107
|
-
def
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
{
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
'options' => { 'separator' => '____' },
|
127
|
-
'outputs' => [{
|
128
|
-
'templatePath' => template_path,
|
129
|
-
'output' => output_filename(lang)
|
130
|
-
}]
|
131
|
-
}
|
51
|
+
def self.merge_strings(paths:, output_path: nil)
|
52
|
+
duplicates = []
|
53
|
+
Tempfile.create('wpmrt-l10n-merge-', encoding: 'utf-8') do |tmp_file|
|
54
|
+
all_keys_found = []
|
55
|
+
|
56
|
+
tmp_file.write("/* Generated File. Do not edit. */\n\n")
|
57
|
+
paths.each do |input_file|
|
58
|
+
fmt = strings_file_type(path: input_file)
|
59
|
+
raise "The file `#{input_file}` does not exist or is of unknown format." if fmt.nil?
|
60
|
+
raise "The file `#{input_file}` is in #{fmt} format but we currently only support merging `.strings` files in text format." unless fmt == :text
|
61
|
+
|
62
|
+
string_keys = read_strings_file_as_hash(path: input_file).keys
|
63
|
+
duplicates += (string_keys & all_keys_found) # Find duplicates using Array intersection, and add those to duplicates list
|
64
|
+
all_keys_found += string_keys
|
65
|
+
|
66
|
+
tmp_file.write("/* MARK: - #{File.basename(input_file)} */\n\n")
|
67
|
+
# 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) }
|
69
|
+
tmp_file.write("\n")
|
132
70
|
end
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
File.write(config_file, config.to_yaml)
|
138
|
-
|
139
|
-
return [config_file, langs]
|
71
|
+
tmp_file.close # ensure we flush the content to disk
|
72
|
+
FileUtils.cp(tmp_file.path, output_path)
|
73
|
+
end
|
74
|
+
duplicates
|
140
75
|
end
|
141
76
|
|
142
|
-
#
|
143
|
-
# We need to sort the output again because SwiftGen only sort case-insensitively so that means keys that are
|
144
|
-
# the same except case might be in swapped order for different outputs
|
77
|
+
# Return the list of translations in a `.strings` file.
|
145
78
|
#
|
146
|
-
# @param [String]
|
147
|
-
# @
|
79
|
+
# @param [String] path The path to the `.strings` file to read
|
80
|
+
# @return [Hash<String,String>] A dictionary of key=>translation translations.
|
81
|
+
# @raise [RuntimeError] If the file is not a valid strings file or there was an error in parsing its content.
|
148
82
|
#
|
149
|
-
def
|
150
|
-
|
151
|
-
|
83
|
+
def self.read_strings_file_as_hash(path:)
|
84
|
+
output, status = Open3.capture2e('/usr/bin/plutil', '-convert', 'json', '-o', '-', path)
|
85
|
+
raise output unless status.success?
|
152
86
|
|
153
|
-
|
154
|
-
File.write(file, sorted_lines.join)
|
155
|
-
return file
|
87
|
+
JSON.parse(output)
|
156
88
|
end
|
157
89
|
|
158
|
-
#
|
90
|
+
# Generate a `.strings` file from a dictionary of string translations.
|
159
91
|
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
# @param [Array<String>] only_langs The list of languages to limit the generation for. Useful to focus only on a couple of issues or just one language
|
163
|
-
# @return [Hash<String, String>] A hash of violations, keyed by language code, whose values are the diff output.
|
92
|
+
# Especially useful to generate `.strings` files not from code, but from keys extracted from another source
|
93
|
+
# (like a JSON file export from GlotPress, or subset of keys extracted from the main `Localizable.strings` to generate an `InfoPlist.strings`)
|
164
94
|
#
|
165
|
-
# @note The
|
95
|
+
# @note The generated file will be in XML-plist format
|
96
|
+
# since ASCII plist is deprecated as an output format by every Apple tool so there's no **safe** way to generate ASCII format.
|
166
97
|
#
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
# and also to serve as a clear visual separator between diff entries in the output.
|
187
|
-
# Those numbers won't be matching the original `.strings` file line numbers because they are line numbers in the SwiftGen-generated intermediate
|
188
|
-
# file instead, but they can still give an indication at the index in the list of keys at which this difference is located.
|
189
|
-
diff.gsub!(/^(---|\+\+\+).*\n/, '')
|
190
|
-
diff.empty? ? nil : [lang, diff]
|
191
|
-
end.compact.to_h
|
98
|
+
# @param [Hash<String,String>] translations The dictionary of key=>translation translations to put in the generated `.strings` file
|
99
|
+
# @param [String] output_path The path to the `.strings` file to generate
|
100
|
+
#
|
101
|
+
def self.generate_strings_file_from_hash(translations:, output_path:)
|
102
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
103
|
+
xml.doc.create_internal_subset(
|
104
|
+
'plist',
|
105
|
+
'-//Apple//DTD PLIST 1.0//EN',
|
106
|
+
'http://www.apple.com/DTDs/PropertyList-1.0.dtd'
|
107
|
+
)
|
108
|
+
xml.comment('Warning: Auto-generated file, do not edit.')
|
109
|
+
xml.plist(version: '1.0') do
|
110
|
+
xml.dict do
|
111
|
+
translations.each do |k, v|
|
112
|
+
xml.key(k.to_s)
|
113
|
+
xml.string(v.to_s)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
192
117
|
end
|
118
|
+
File.write(output_path, builder.to_xml)
|
193
119
|
end
|
194
120
|
|
195
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
121
|
+
# Downloads the export from GlotPress for a given locale and given filters.
|
122
|
+
#
|
123
|
+
# @param [String] project_url The URL to the GlotPress project to export from, e.g. `"https://translate.wordpress.org/projects/apps/ios/dev"`
|
124
|
+
# @param [String] locale The GlotPress locale code to download strings for.
|
125
|
+
# @param [Hash{Symbol=>String}] filters The hash of filters to apply when exporting from GlotPress.
|
126
|
+
# Typical examples include `{ status: 'current' }` or `{ status: 'review' }`.
|
127
|
+
# @param [String, IO] destination The path or `IO`-like instance, where to write the downloaded file on disk.
|
128
|
+
#
|
129
|
+
def self.download_glotpress_export_file(project_url:, locale:, filters:, destination:)
|
130
|
+
query_params = (filters || {}).transform_keys { |k| "filters[#{k}]" }.merge(format: 'strings')
|
131
|
+
uri = URI.parse("#{project_url.chomp('/')}/#{locale}/default/export-translations?#{URI.encode_www_form(query_params)}")
|
132
|
+
begin
|
133
|
+
IO.copy_stream(uri.open, destination)
|
134
|
+
rescue StandardError => e
|
135
|
+
UI.error "Error downloading locale `#{locale}` — #{e.message}"
|
136
|
+
return nil
|
201
137
|
end
|
202
|
-
return true
|
203
138
|
end
|
204
139
|
end
|
205
140
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Helper
|
6
|
+
module Ios
|
7
|
+
class L10nLinterHelper
|
8
|
+
SWIFTGEN_VERSION = '6.4.0'
|
9
|
+
DEFAULT_BASE_LANG = 'en'
|
10
|
+
CONFIG_FILE_NAME = 'swiftgen-stringtypes.yml'
|
11
|
+
|
12
|
+
attr_reader :install_path, :version
|
13
|
+
|
14
|
+
# @param [String] install_path The path to install SwiftGen to. Usually something like "$PROJECT_DIR/vendor/swiftgen/#{SWIFTGEN_VERSION}".
|
15
|
+
# It's recommended to provide an absolute path here rather than a relative one, to ensure it's not dependant on where the action is run from.
|
16
|
+
# @param [String] version The version of SwiftGen to use. This will be used both:
|
17
|
+
# - to check if the current version located in `install_path`, if it already exists, is the expected one
|
18
|
+
# - to know which version to download if there is not one installed in `install_path` yet
|
19
|
+
#
|
20
|
+
def initialize(install_path:, version: SWIFTGEN_VERSION)
|
21
|
+
@install_path = install_path
|
22
|
+
@version = version || SWIFTGEN_VERSION
|
23
|
+
end
|
24
|
+
|
25
|
+
# Check if SwiftGen is installed in the provided `install_path` and if so if the installed version matches the expected `version`
|
26
|
+
#
|
27
|
+
def check_swiftgen_installed
|
28
|
+
return false unless File.exist?(swiftgen_bin)
|
29
|
+
|
30
|
+
vers_string = `#{swiftgen_bin} --version`
|
31
|
+
# The SwiftGen version string has this format:
|
32
|
+
#
|
33
|
+
# SwiftGen v6.4.0 (Stencil v0.13.1, StencilSwiftKit v2.7.2, SwiftGenKit v6.4.0)
|
34
|
+
return vers_string.include?("SwiftGen v#{version}")
|
35
|
+
rescue
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Download the ZIP of SwiftGen for the requested `version` and install it in the `install_path`
|
40
|
+
#
|
41
|
+
# @note This action nukes anything at `install_path` – if something already exists – prior to install SwiftGen there
|
42
|
+
#
|
43
|
+
def install_swiftgen!
|
44
|
+
UI.message "Installing SwiftGen #{version} into #{install_path}"
|
45
|
+
Dir.mktmpdir do |tmpdir|
|
46
|
+
zipfile = File.join(tmpdir, "swiftgen-#{version}.zip")
|
47
|
+
Action.sh('curl', '--fail', '--location', '-o', zipfile, "https://github.com/SwiftGen/SwiftGen/releases/download/#{version}/swiftgen-#{version}.zip")
|
48
|
+
extracted_dir = File.join(tmpdir, "swiftgen-#{version}")
|
49
|
+
Action.sh('unzip', zipfile, '-d', extracted_dir)
|
50
|
+
|
51
|
+
FileUtils.rm_rf(install_path) if File.exist?(install_path)
|
52
|
+
FileUtils.mkdir_p(install_path)
|
53
|
+
FileUtils.cp_r("#{extracted_dir}/.", install_path)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Install SwiftGen if necessary (if not installed yet with the expected version), then run the checks and returns the violations found, if any
|
58
|
+
#
|
59
|
+
# @param [String] input_dir The path (ideally absolute) to the directory containing the `.lproj` folders to parse
|
60
|
+
# @param [String] base_lang The code name (i.e the basename of one of the `.lproj` folders) of the locale to use as the baseline
|
61
|
+
# @return [Hash<String, String>] A hash whose keys are the language codes (basename of `.lproj` folders) for which violations were found,
|
62
|
+
# and the values are the output of the `diff` showing these violations.
|
63
|
+
#
|
64
|
+
def run(input_dir:, base_lang: DEFAULT_BASE_LANG, only_langs: nil)
|
65
|
+
check_swiftgen_installed || install_swiftgen!
|
66
|
+
find_diffs(input_dir: input_dir, base_lang: base_lang, only_langs: only_langs)
|
67
|
+
end
|
68
|
+
|
69
|
+
##################
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Path to the swiftgen binary installed at install_path
|
74
|
+
def swiftgen_bin
|
75
|
+
"#{install_path}/bin/swiftgen"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Name to use for the generated files / output files of SwiftGen for each locale. Those files will be generated in the temporary directory to then diff them.
|
79
|
+
def output_filename(lang)
|
80
|
+
"L10nParamsList.#{lang}.txt"
|
81
|
+
end
|
82
|
+
|
83
|
+
# The Stencil template that we want SwiftGen to use to generate the output.
|
84
|
+
# It iterates on every "table" (`.strings` file, in most cases there's only one, `Localizable.strings`),
|
85
|
+
# and for each, iterates on every entry found to print the key and the corresponding types parsed by SwiftGen from the placeholders found in that translation
|
86
|
+
def template_content
|
87
|
+
<<~TEMPLATE
|
88
|
+
{% macro recursiveBlock table item %}
|
89
|
+
{% for string in item.strings %}
|
90
|
+
"{{string.key}}" => [{{string.types|join:","}}]
|
91
|
+
{% endfor %}
|
92
|
+
{% for child in item.children %}
|
93
|
+
{% call recursiveBlock table child %}
|
94
|
+
{% endfor %}
|
95
|
+
{% endmacro %}
|
96
|
+
|
97
|
+
{% for table in tables %}
|
98
|
+
{% call recursiveBlock table.name table.levels %}
|
99
|
+
{% endfor %}
|
100
|
+
TEMPLATE
|
101
|
+
end
|
102
|
+
|
103
|
+
# Create the template file and the config file, in the temp dir, to be used by SwiftGen when parsing the input files.
|
104
|
+
#
|
105
|
+
# @return [(String, Array<String>)] A tuple of (config_file_absolute_path, Array<langs>)
|
106
|
+
#
|
107
|
+
def generate_swiftgen_config!(input_dir, output_dir, only_langs: nil)
|
108
|
+
# Create the template file
|
109
|
+
template_path = File.absolute_path(File.join(output_dir, 'strings-types.stencil'))
|
110
|
+
File.write(template_path, template_content)
|
111
|
+
|
112
|
+
# Dynamically create a SwiftGen config which will cover all supported languages
|
113
|
+
langs = Dir.chdir(input_dir) do
|
114
|
+
Dir.glob('*.lproj/Localizable.strings').map { |loc_file| File.basename(File.dirname(loc_file), '.lproj') }
|
115
|
+
end.sort
|
116
|
+
langs.select! { |lang| only_langs.include?(lang) } unless only_langs.nil?
|
117
|
+
|
118
|
+
config = {
|
119
|
+
'input_dir' => input_dir,
|
120
|
+
'output_dir' => output_dir,
|
121
|
+
'strings' => langs.map do |lang|
|
122
|
+
{
|
123
|
+
'inputs' => ["#{lang}.lproj/Localizable.strings"],
|
124
|
+
# Choose an unlikely separator (instead of the default '.') to avoid creating needlessly complex Stencil Context nested
|
125
|
+
# structure just because we have '.' in the English sentences we use (instead of structured reverse-dns notation) for the keys
|
126
|
+
'options' => { 'separator' => '____' },
|
127
|
+
'outputs' => [{
|
128
|
+
'templatePath' => template_path,
|
129
|
+
'output' => output_filename(lang)
|
130
|
+
}]
|
131
|
+
}
|
132
|
+
end
|
133
|
+
}
|
134
|
+
|
135
|
+
# Write SwiftGen config file
|
136
|
+
config_file = File.join(output_dir, CONFIG_FILE_NAME)
|
137
|
+
File.write(config_file, config.to_yaml)
|
138
|
+
|
139
|
+
return [config_file, langs]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Because we use English copy verbatim as key names, some keys are the same except for the upper/lowercase.
|
143
|
+
# We need to sort the output again because SwiftGen only sort case-insensitively so that means keys that are
|
144
|
+
# the same except case might be in swapped order for different outputs
|
145
|
+
#
|
146
|
+
# @param [String] dir The temporary directory in which the file to sort lines for is located
|
147
|
+
# @param [String] lang The code for the locale we need to sort the output lines for
|
148
|
+
#
|
149
|
+
def sort_file_lines!(dir, lang)
|
150
|
+
file = File.join(dir, output_filename(lang))
|
151
|
+
return nil unless File.exist?(file)
|
152
|
+
|
153
|
+
sorted_lines = File.readlines(file).sort
|
154
|
+
File.write(file, sorted_lines.join)
|
155
|
+
return file
|
156
|
+
end
|
157
|
+
|
158
|
+
# Prepares the template and config files, then run SwiftGen, run `diff` on each generated output against the baseline, and returns a Hash of the violations found.
|
159
|
+
#
|
160
|
+
# @param [String] input_dir The directory where the `.lproj` folders to scan are located
|
161
|
+
# @param [String] base_lang The base language used as source of truth that all other languages will be compared against
|
162
|
+
# @param [Array<String>] only_langs The list of languages to limit the generation for. Useful to focus only on a couple of issues or just one language
|
163
|
+
# @return [Hash<String, String>] A hash of violations, keyed by language code, whose values are the diff output.
|
164
|
+
#
|
165
|
+
# @note The returned Hash contains keys only for locales with violations. Locales parsed but without any violations found will not appear in the resulting hash.
|
166
|
+
#
|
167
|
+
def find_diffs(input_dir:, base_lang:, only_langs: nil)
|
168
|
+
Dir.mktmpdir('a8c-lint-translations-') do |tmpdir|
|
169
|
+
# Run SwiftGen
|
170
|
+
langs = only_langs.nil? ? nil : (only_langs + [base_lang]).uniq
|
171
|
+
(config_file, langs) = generate_swiftgen_config!(input_dir, tmpdir, only_langs: langs)
|
172
|
+
Action.sh(swiftgen_bin, 'config', 'run', '--config', config_file)
|
173
|
+
|
174
|
+
# Run diffs
|
175
|
+
base_file = sort_file_lines!(tmpdir, base_lang)
|
176
|
+
langs.delete(base_lang)
|
177
|
+
return langs.map do |lang|
|
178
|
+
file = sort_file_lines!(tmpdir, lang)
|
179
|
+
# If the lang ends up not having any translation at all (e.g. a `.lproj` without any `.strings` file in it but maybe just a storyboard or assets catalog), ignore it
|
180
|
+
next nil if file.nil? || only_empty_lines?(file)
|
181
|
+
|
182
|
+
# Compute the diff
|
183
|
+
diff = `diff -U0 "#{base_file}" "#{file}"`
|
184
|
+
# Remove the lines starting with `---`/`+++` which contains the file names (which are temp files we don't want to expose in the final diff to users)
|
185
|
+
# Note: We still keep the `@@ from-file-line-numbers to-file-line-numbers @@` lines to help the user know the index of the key to find it faster,
|
186
|
+
# and also to serve as a clear visual separator between diff entries in the output.
|
187
|
+
# Those numbers won't be matching the original `.strings` file line numbers because they are line numbers in the SwiftGen-generated intermediate
|
188
|
+
# file instead, but they can still give an indication at the index in the list of keys at which this difference is located.
|
189
|
+
diff.gsub!(/^(---|\+\+\+).*\n/, '')
|
190
|
+
diff.empty? ? nil : [lang, diff]
|
191
|
+
end.compact.to_h
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns true if the file only contains empty lines, i.e. lines that only contains whitespace (space, tab, CR, LF)
|
196
|
+
def only_empty_lines?(file)
|
197
|
+
File.open(file) do |f|
|
198
|
+
while (line = f.gets)
|
199
|
+
return false unless line.strip.empty?
|
200
|
+
end
|
201
|
+
end
|
202
|
+
return true
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -28,6 +28,8 @@ module Fastlane
|
|
28
28
|
message << 'Aborting.'
|
29
29
|
UI.user_error!(message)
|
30
30
|
end
|
31
|
+
|
32
|
+
UI.user_error!('`drawText` not found – install it using `brew install automattic/build-tools/drawText`.') unless system('command -v drawText')
|
31
33
|
end
|
32
34
|
|
33
35
|
def read_json(configFilePath)
|
@@ -233,9 +235,7 @@ module Fastlane
|
|
233
235
|
begin
|
234
236
|
tempTextFile = Tempfile.new()
|
235
237
|
|
236
|
-
|
237
|
-
|
238
|
-
UI.crash!('Unable to draw text') unless system(command)
|
238
|
+
sh('drawText', "html=\"#{text}\"", "maxWidth=#{width}", "maxHeight=#{height}", "output=\"#{tempTextFile.path}\"", "fontSize=#{font_size}", "stylesheet=\"#{stylesheet_path}\"", "alignment=\"#{position}\"")
|
239
239
|
|
240
240
|
text_content = open_image(tempTextFile.path).trim
|
241
241
|
text_frame = create_image(width, height)
|
@@ -10,10 +10,7 @@ module Fastlane
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def source_contents
|
13
|
-
|
14
|
-
# Fastlane::is_ci doesn't work here, so reimplementing the code has been necessary.
|
15
|
-
# (This should be updated if we change CI or if fastlane is updated.)
|
16
|
-
return File.read(secrets_repository_file_path) unless self.encrypt || ENV.key?('CIRCLECI')
|
13
|
+
return File.read(secrets_repository_file_path) unless self.encrypt || FastlaneCore::Helper.is_ci?
|
17
14
|
return nil unless File.file?(encrypted_file_path)
|
18
15
|
|
19
16
|
encrypted = File.read(encrypted_file_path)
|