Skip to content

Commit daba04d

Browse files
authored
Merge pull request #353 from wordpress-mobile/fix-ios-linter
Fix `ios_l10n_linter` to allow missing translations in `.strings` files not being flagged as violations
2 parents 247f0ec + 3bbe315 commit daba04d

File tree

8 files changed

+81
-111
lines changed

8 files changed

+81
-111
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ _None_
1616

1717
### Bug Fixes
1818

19-
_None_
19+
* Fix `ios_lint_localizations` action so that it no longer mistakely reports missing keys not yet translated in the other locales' `.strings` as violations. [#353]
2020

2121
### Internal Changes
2222

lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_lint_localizations.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ def self.run_linter(params)
2222
install_path: resolve_path(params[:install_path]),
2323
version: params[:version]
2424
)
25-
violations = helper.run(
25+
all_violations = helper.run(
2626
input_dir: resolve_path(params[:input_dir]),
2727
base_lang: params[:base_lang],
2828
only_langs: params[:only_langs]
2929
)
3030

31-
violations.each do |lang, diff|
32-
UI.error "Inconsistencies found between '#{params[:base_lang]}' and '#{lang}':\n\n#{diff}\n"
31+
all_violations.each do |lang, lang_violations|
32+
UI.error "Inconsistencies found between '#{params[:base_lang]}' and '#{lang}':\n\n#{lang_violations.join("\n")}\n"
3333
end
3434

35-
violations
35+
all_violations
3636
end
3737

3838
RETRY_MESSAGE = <<~MSG
@@ -148,15 +148,15 @@ def self.output
148148
end
149149

150150
def self.return_type
151-
:hash_of_strings
151+
:hash
152152
end
153153

154154
def self.return_value
155-
'A hash, keyed by language code, whose values are the diff found for said language'
155+
'A hash, keyed by language code, whose values are arrays of violations found for that language'
156156
end
157157

158158
def self.authors
159-
['AliSoftware']
159+
['Automattic']
160160
end
161161

162162
def self.is_supported?(platform)

lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_linter_helper.rb

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ def install_swiftgen!
5858
#
5959
# @param [String] input_dir The path (ideally absolute) to the directory containing the `.lproj` folders to parse
6060
# @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.
61+
# @return [Hash<String, Array<String>>] A hash of violations, keyed by language code, whose values are the list of violation messages for that language
6362
#
6463
def run(input_dir:, base_lang: DEFAULT_BASE_LANG, only_langs: nil)
6564
check_swiftgen_installed || install_swiftgen!
@@ -87,7 +86,7 @@ def template_content
8786
<<~TEMPLATE
8887
{% macro recursiveBlock table item %}
8988
{% for string in item.strings %}
90-
"{{string.key}}" => [{{string.types|join:","}}]
89+
{{string.key}} ==> [{{string.types|join:","}}]
9190
{% endfor %}
9291
{% for child in item.children %}
9392
{% call recursiveBlock table child %}
@@ -139,28 +138,27 @@ def generate_swiftgen_config!(input_dir, output_dir, only_langs: nil)
139138
return [config_file, langs]
140139
end
141140

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
141+
# Returns a Hash mapping the list of expected parameter types for each of the keys based in the %… placeholders found in their `.strings` files
145142
#
146143
# @param [String] dir The temporary directory in which the file to sort lines for is located
147144
# @param [String] lang The code for the locale we need to sort the output lines for
145+
# @return [Hash<String, String>] A hash whose keys are the strings keys, and corresponding value is a String describing the types expected as parameters.
148146
#
149-
def sort_file_lines!(dir, lang)
147+
def placeholder_types_for_keys(dir, lang)
150148
file = File.join(dir, output_filename(lang))
151149
return nil unless File.exist?(file)
152150

153-
sorted_lines = File.readlines(file).sort
154-
File.write(file, sorted_lines.join)
155-
return file
151+
File.readlines(file).map do |line|
152+
line.match(/^(.*) ==> (\[[A-Za-z,]*\])$/)&.captures
153+
end.compact.to_h
156154
end
157155

158156
# 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.
159157
#
160158
# @param [String] input_dir The directory where the `.lproj` folders to scan are located
161159
# @param [String] base_lang The base language used as source of truth that all other languages will be compared against
162160
# @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.
161+
# @return [Hash<String, Array<String>>] A hash of violations, keyed by language code, whose values are the list of violation messages for that language
164162
#
165163
# @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.
166164
#
@@ -172,34 +170,22 @@ def find_diffs(input_dir:, base_lang:, only_langs: nil)
172170
Action.sh(swiftgen_bin, 'config', 'run', '--config', config_file)
173171

174172
# Run diffs
175-
base_file = sort_file_lines!(tmpdir, base_lang)
173+
params_for_base_lang = placeholder_types_for_keys(tmpdir, base_lang)
176174
langs.delete(base_lang)
177175
return langs.map do |lang|
178-
file = sort_file_lines!(tmpdir, lang)
176+
params_for_lang = placeholder_types_for_keys(tmpdir, lang)
177+
179178
# 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
179+
next nil if params_for_lang.nil? || params_for_lang.empty?
194180

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
181+
violations = params_for_lang.map do |key, param_types|
182+
next "`#{key}` was unexpected, as it is not present in the base locale." if params_for_base_lang[key].nil?
183+
next "`#{key}` expected placeholders for #{params_for_base_lang[key]} but found #{param_types} instead." if params_for_base_lang[key] != param_types
184+
end.compact
185+
186+
[lang, violations] unless violations.empty?
187+
end.compact.to_h
201188
end
202-
return true
203189
end
204190
end
205191
end

spec/ios_lint_localizations_spec.rb

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,34 @@
5454
end
5555

5656
context 'Linter' do
57+
def run_l10n_linter_test(data_file)
58+
# Arrange: Prepare test files
59+
test_file = File.join(File.dirname(__FILE__), 'test-data', 'translations', "test-lint-ios-#{data_file}.yaml")
60+
yml = YAML.load_file(test_file)
61+
62+
files = yml['test_data']
63+
FileUtils.mkdir_p(@test_data_dir)
64+
files.each do |lang, content|
65+
lproj = File.join(@test_data_dir, "#{lang}.lproj")
66+
FileUtils.mkdir_p(lproj)
67+
File.write(File.join(lproj, 'Localizable.strings'), content) unless content.nil?
68+
end
69+
70+
# Act
71+
# Note: We will install SwiftGen in vendor/swiftgen if it's not already installed yet, and intentionally won't
72+
# remove this after the test ends, so that further executions of the test run faster.
73+
# Only the first execution of the tests might take longer if it needs to install SwiftGen first to be able to run the tests.
74+
install_dir = "vendor/swiftgen/#{Fastlane::Helper::Ios::L10nLinterHelper::SWIFTGEN_VERSION}"
75+
result = run_described_fastlane_action(
76+
install_path: install_dir,
77+
input_dir: @test_data_dir,
78+
base_lang: 'en'
79+
)
80+
81+
# Assert
82+
expect(result).to eq(yml['result'])
83+
end
84+
5785
before(:each) do
5886
@test_data_dir = Dir.mktmpdir('a8c-lint-l10n-tests-data-')
5987
allow(FastlaneCore::UI).to receive(:abort_with_message!)
@@ -84,31 +112,3 @@
84112
end
85113
end
86114
end
87-
88-
def run_l10n_linter_test(data_file)
89-
# Arrange: Prepare test files
90-
test_file = File.join(File.dirname(__FILE__), 'test-data', 'translations', "test-lint-ios-#{data_file}.yaml")
91-
yml = YAML.load_file(test_file)
92-
93-
files = yml['test_data']
94-
FileUtils.mkdir_p(@test_data_dir)
95-
files.each do |lang, content|
96-
lproj = File.join(@test_data_dir, "#{lang}.lproj")
97-
FileUtils.mkdir_p(lproj)
98-
File.write(File.join(lproj, 'Localizable.strings'), content) unless content.nil?
99-
end
100-
101-
# Act
102-
# Note: We will install SwiftGen in vendor/swiftgen if it's not already installed yet, and intentionally won't
103-
# remove this after the test ends, so that further executions of the test run faster.
104-
# Only the first execution of the tests might take longer if it needs to install SwiftGen first to be able to run the tests.
105-
install_dir = "vendor/swiftgen/#{Fastlane::Helper::Ios::L10nLinterHelper::SWIFTGEN_VERSION}"
106-
result = run_described_fastlane_action(
107-
install_path: install_dir,
108-
input_dir: @test_data_dir,
109-
base_lang: 'en'
110-
)
111-
112-
# Assert
113-
expect(result).to eq(yml['result'])
114-
end

spec/test-data/translations/test-lint-ios-no-violations.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ test_data:
44
"string_pos" = "Pos String %1$@ here.";
55
"string_int_pos" = "String %1$@ and Int %2$d";
66
"string_int_pos_inv" = "Int %2$d before String %1$@";
7-
"many_placeholders_mix_pos" = "String %2$@, Float %4$f, Int %1$d, Long %5$li, Precise %3$.3lf";
7+
"many_placeholders_mix_pos" = "String %2$@, Float %4$f,\n Int %1$d, \
8+
Long %5$li, Precise %3$.3lf";
9+
"extra_string_not_translated" = "This string might not have a translation yet in %1$d of our locales.";
810
"repeated_placeholder" = "String %1$@, yes, I repeat, that's %1$@ indeed, told you %2$d times.";
911
fr: |
1012
"string_no_pos" = "String %@ here.";
1113
"string_pos" = "Pos String %1$@ here.";
1214
"string_int_pos" = "String %1$@ and Int %2$d";
1315
"string_int_pos_inv" = "Int %2$d before String %1$@";
14-
"many_placeholders_mix_pos" = "String %2$@, Float %4$f, Int %1$d, Long %5$li, Precise %3$.3lf";
16+
"many_placeholders_mix_pos" = "String %2$@, Float %4$f,\
17+
Int %1$d, \n Long %5$li, Precise %3$.3lf";
1518
"repeated_placeholder" = "String %1$@, yes, that's it indeed, won't tell you %2$d times.";
1619
it: |
1720
"string_no_pos" = "String %@ here.";

spec/test-data/translations/test-lint-ios-tricky-chars.yaml

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,9 @@ test_data:
2323
"repeated_placeholder" = "السلسلة %1$@ ، نعم ، أكرر ، هذه %1$@ بالفعل ، وليست %2$d."; // OK
2424
2525
result:
26-
fr: |
27-
@@ -3 +3 @@
28-
-"many_placeholders_mix_pos" => [Int,String,Float,Float,Int]
29-
+"many_placeholders_mix_pos" => [String,Float,Float,Int]
30-
@@ -5 +5 @@
31-
-"string_int_pos" => [String,Int]
32-
+"string_int_pos" => [Int]
33-
@@ -7 +7 @@
34-
-"string_no_pos" => [String]
35-
+"string_no_pos" => []
36-
ar: |
37-
@@ -8 +8 @@
38-
-"string_pos" => [String]
39-
+"string_pos" => []
26+
fr:
27+
- "`many_placeholders_mix_pos` expected placeholders for [Int,String,Float,Float,Int] but found [String,Float,Float,Int] instead."
28+
- "`string_int_pos` expected placeholders for [String,Int] but found [Int] instead."
29+
- "`string_no_pos` expected placeholders for [String] but found [] instead."
30+
ar:
31+
- "`string_pos` expected placeholders for [String] but found [] instead."

spec/test-data/translations/test-lint-ios-wrong-placeholder-count.yaml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ test_data:
1212
"string_int_pos" = "String %1$@ and Int %2$d";
1313
"string_int_pos_inv" = "Int %2$d before String %1$@";
1414
"many_placeholders_mix_pos" = "String %2$@, Float %4$f, Int %1$d, Long %5$li, Precise %3$.3lf";
15+
"unexpected_key_not_present_in_base" = "This key should not exist in the translation given it does not exist in English";
1516
"repeated_placeholder" = "String %1$@, yes, that's it indeed, %@.";
1617
it: |
1718
"string_no_pos" = "String %@ here.";
@@ -21,12 +22,9 @@ test_data:
2122
"many_placeholders_mix_pos" = "String %2$@, Float %4$f, Int %1$d, Long %5$li, Precise %3$.3lf";
2223
"repeated_placeholder" = "String %1$@, yes, and not %2%@, I repeat, that's not %2$@.";
2324
24-
result:
25-
fr: |
26-
@@ -7 +7 @@
27-
-"string_no_pos" => [String]
28-
+"string_no_pos" => [String,String]
29-
it: |
30-
@@ -4 +4 @@
31-
-"repeated_placeholder" => [String]
32-
+"repeated_placeholder" => [String,String]
25+
result:
26+
fr:
27+
- "`string_no_pos` expected placeholders for [String] but found [String,String] instead."
28+
- "`unexpected_key_not_present_in_base` was unexpected, as it is not present in the base locale."
29+
it:
30+
- "`repeated_placeholder` expected placeholders for [String] but found [String,String] instead."

spec/test-data/translations/test-lint-ios-wrong-placeholder-types.yaml

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,10 @@ test_data:
2121
"many_placeholders_mix_pos" = "String %2$@, Float %4$f, Int %1$d, Long %5$li, Precise %3$.3lf";
2222
"repeated_placeholder" = "String %1$d, yes, I repeat, that's %1$d indeed, I said %d.";
2323
24-
result:
25-
fr: |
26-
@@ -3 +3 @@
27-
-"many_placeholders_mix_pos" => [Int,String,Float,Int]
28-
+"many_placeholders_mix_pos" => [String,Float,Int,Int]
29-
@@ -5 +5 @@
30-
-"string_int_pos" => [String,Int]
31-
+"string_int_pos" => [Int,String]
32-
@@ -7 +7 @@
33-
-"string_no_pos" => [String]
34-
+"string_no_pos" => [Int]
35-
36-
it: |
37-
@@ -4 +4 @@
38-
-"repeated_placeholder" => [String]
39-
+"repeated_placeholder" => [Int]
24+
result:
25+
fr:
26+
- "`many_placeholders_mix_pos` expected placeholders for [Int,String,Float,Int] but found [String,Float,Int,Int] instead."
27+
- "`string_int_pos` expected placeholders for [String,Int] but found [Int,String] instead."
28+
- "`string_no_pos` expected placeholders for [String] but found [Int] instead."
29+
it:
30+
- "`repeated_placeholder` expected placeholders for [String] but found [Int] instead."

0 commit comments

Comments
 (0)