Skip to content

Commit fc2b121

Browse files
committed
Read .po nplurals from the parsed header, not a raw-file scan
`declared_nplurals` re-read the `.po` line by line and returned the first `nplurals=` it found anywhere, so a stray occurrence before the real `Plural-Forms` header (a comment, a translated string) would be read as the form count and make `guard_po_plural_count!` abort with a spurious error. Real GlotPress exports were unaffected, but the scan was needlessly fragile — and redundant, since the `.po` is already parsed by then. Read `nplurals` from the parsed header entry (`po[nil, '']`), whose `msgstr` holds only the header fields, so no token elsewhere in the file can be mistaken for it. Drops the extra file read too.
1 parent 01483c4 commit fc2b121

2 files changed

Lines changed: 33 additions & 11 deletions

File tree

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def self.generate_stringsdict_from_po(po_path:, template_path:, locale:, output_
146146
# has. If the .po's own declared count disagrees with our mapping,
147147
# GlotPress has changed its plural rule for this locale — fail loud
148148
# (the signal to regenerate PluralRules) rather than silently mis-map.
149-
guard_po_plural_count!(po_path: po_path, locale: locale, categories: categories)
149+
guard_po_plural_count!(parsed_po: po, locale: locale, categories: categories)
150150

151151
missing = []
152152
result = {}
@@ -336,21 +336,22 @@ def self.parse_po(path)
336336

337337
# The `nplurals` value declared in the `.po`'s `Plural-Forms` header
338338
# (GlotPress's authoritative form count for the locale), or `nil` if the
339-
# header is absent. Read straight from the file so it doesn't depend on
340-
# how the parser exposes the header entry.
341-
def self.declared_nplurals(po_path)
342-
File.foreach(po_path) do |line|
343-
m = line.match(/nplurals\s*=\s*(\d+)/)
344-
return Integer(m[1]) if m
345-
end
346-
nil
339+
# header is absent. Read from the parsed header entry, whose `msgstr`
340+
# holds only the header fields — so a stray `nplurals=` token elsewhere
341+
# in the file (a comment, a translated string) can't be mistaken for it.
342+
def self.declared_nplurals(parsed_po)
343+
header = parsed_po[nil, '']
344+
return nil if header.nil? || header.msgstr.nil?
345+
346+
match = header.msgstr.match(/nplurals\s*=\s*(\d+)/)
347+
match && Integer(match[1])
347348
end
348349

349350
# Fail loud if the `.po`'s declared plural-form count doesn't match the
350351
# number of CLDR categories we expect for the locale — i.e. GlotPress's
351352
# plural rule has drifted from {PluralRules}.
352-
def self.guard_po_plural_count!(po_path:, locale:, categories:)
353-
declared = declared_nplurals(po_path)
353+
def self.guard_po_plural_count!(parsed_po:, locale:, categories:)
354+
declared = declared_nplurals(parsed_po)
354355
return if declared.nil? || declared == categories.size
355356

356357
UI.user_error!("Locale '#{locale}': the .po declares nplurals=#{declared}, but the expected plural mapping has " \

spec/ios_stringsdict_helper_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,5 +353,26 @@ def convert_po_to_stringsdict(po_content, template:, locale:)
353353
convert_po_to_stringsdict(simple_po(forms: %w[a b], nplurals: 2), template: simple, locale: 'ru')
354354
end.to raise_error(FastlaneCore::Interface::FastlaneError, /declares nplurals=2.*3 categor/m)
355355
end
356+
357+
it 'reads nplurals from the header, not a stray `nplurals=` elsewhere in the .po' do
358+
po = <<~PO
359+
# A misleading comment that mentions nplurals=4 must not be picked up.
360+
msgid ""
361+
msgstr ""
362+
"Plural-Forms: nplurals=3; plural=(n != 1);\\n"
363+
364+
msgctxt "%d items"
365+
msgid "%d item"
366+
msgid_plural "%d items"
367+
msgstr[0] "ru-one"
368+
msgstr[1] "ru-few"
369+
msgstr[2] "ru-many"
370+
PO
371+
data, missing = convert_po_to_stringsdict(po, template: simple, locale: 'ru')
372+
expect(missing).to be_empty
373+
expect(data['%d items']['count'].slice('one', 'few', 'many')).to eq(
374+
'one' => 'ru-one', 'few' => 'ru-few', 'many' => 'ru-many'
375+
)
376+
end
356377
end
357378
end

0 commit comments

Comments
 (0)