Skip to content

Commit a7b17fd

Browse files
committed
Warn when a locale's stringsdict entry is only partially translated
A `.po` entry with some — but not all — plural forms filled in passes the form-count check, so the reverse conversion mapped what it had, omitted the blank categories, and back-filled `other` from a sibling form — silently. iOS then shows the wrong plural for the untranslated counts (a Russian entry missing `many` renders the `few` text for 5, 6, 7, …), with nothing in the `missing` report to flag it. `warn_partial_translation` now emits a `UI.important` naming the locale, context, and which CLDR forms are untranslated. Partial entries stay out of the `missing` list (which means "no translation at all"), so the two cases aren't conflated. Output and the returned list are unchanged — the tool can't supply the absent translation, only make the gap visible so a translator finishes it.
1 parent 9a69539 commit a7b17fd

3 files changed

Lines changed: 35 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ _None_
1111
### New Features
1212

1313
- Added `find_or_create_pull_request` action and `GithubHelper#find_pull_request`: returns the URL of the open Pull Request for a head branch, creating one only if none exists yet. Useful for "rolling" automations (e.g. a daily translations or dependency-update job) that force-push the same head branch on every run. [#733]
14-
- Added `ios_generate_pot_from_stringsdict` and `ios_generate_stringsdict_from_po` actions to round-trip iOS `.stringsdict` plural files through a gettext `.po`/`.pot`-based translation system (e.g. GlotPress). The forward action turns an English `.stringsdict` into a `.pot` template (one `msgid`/`msgid_plural` per plural variable); the reverse rebuilds a per-locale `.stringsdict` from a translated `.po`, mapping gettext's indexed plural forms back to CLDR plural categories (`one`/`few`/`many`/…) via the new `Ios::PluralRules` table and using the English `.stringsdict` as a structural template. The forward action round-trips only the `one`/`other` forms; other CLDR categories in the source (e.g. a `zero` literal override) are dropped with a warning. [#739]
14+
- Added `ios_generate_pot_from_stringsdict` and `ios_generate_stringsdict_from_po` actions to round-trip iOS `.stringsdict` plural files through a gettext `.po`/`.pot`-based translation system (e.g. GlotPress). The forward action turns an English `.stringsdict` into a `.pot` template (one `msgid`/`msgid_plural` per plural variable); the reverse rebuilds a per-locale `.stringsdict` from a translated `.po`, mapping gettext's indexed plural forms back to CLDR plural categories (`one`/`few`/`many`/…) via the new `Ios::PluralRules` table and using the English `.stringsdict` as a structural template. The forward action round-trips only the `one`/`other` forms; other CLDR categories in the source (e.g. a `zero` literal override) are dropped with a warning. The reverse action likewise warns when a locale's plural entry is only partially translated. [#739]
1515

1616
### Bug Fixes
1717

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def self.generate_stringsdict_from_po(po_path:, template_path:, locale:, output_
164164
localized[var_name] = english_variable(var_dict)
165165
else
166166
validate_form_count!(forms: forms, categories: categories, context: context, locale: locale)
167+
warn_partial_translation(context: context, locale: locale, categories: categories, forms: forms)
167168
localized[var_name] = localized_variable(source_var: var_dict, categories: categories, forms: forms)
168169
end
169170
end
@@ -260,6 +261,22 @@ def self.validate_form_count!(forms:, categories:, context:, locale:)
260261
'configuration for this locale does not match the expected CLDR categories.'
261262
end
262263

264+
# A translated entry that has some — but not all — of its plural forms
265+
# filled still passes the count check. The blank categories are omitted
266+
# from the `.stringsdict`, so iOS falls back to another form for those
267+
# counts, silently showing the wrong plural. Surface it the way the
268+
# forward path surfaces dropped categories, rather than shipping it.
269+
def self.warn_partial_translation(context:, locale:, categories:, forms:)
270+
blank = categories.each_index.select { |i| forms[i].nil? || forms[i].to_s.empty? }.map { |i| categories[i] }
271+
return if blank.empty?
272+
273+
UI.important(
274+
"Translation for '#{context}' (locale '#{locale}') is incomplete — the " \
275+
"#{blank.join(', ')} plural form(s) are untranslated; those counts will fall " \
276+
'back to another form. Finish the translation so every count shows the right plural.'
277+
)
278+
end
279+
263280
# Build a localized variable dictionary by mapping indexed forms to CLDR
264281
# categories and copying structure from the source variable.
265282
def self.localized_variable(source_var:, categories:, forms:)

spec/ios_stringsdict_helper_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,23 @@ def convert_po_to_stringsdict(po_content, template:, locale:)
257257
expect(data['%d items']['count']).to include('one' => '%d item', 'other' => '%d items')
258258
end
259259

260+
it 'warns when a locale entry is only partially translated' do
261+
expect(FastlaneCore::UI).to receive(:important) do |message|
262+
expect(message).to include('many')
263+
expect(message).to match(/incomplete|untranslated/i)
264+
end
265+
# `ru` needs one/few/many; the translator left `many` blank.
266+
data, missing = convert_po_to_stringsdict(
267+
simple_po(forms: ['ru-one', 'ru-few', ''], nplurals: 3),
268+
template: simple, locale: 'ru'
269+
)
270+
# Not reported as fully-missing — it does have *some* translation…
271+
expect(missing).to be_empty
272+
# …but the untranslated `many` is absent (falls back), not silently wrong-but-present.
273+
expect(data['%d items']['count']).not_to have_key('many')
274+
expect(data['%d items']['count']).to include('one' => 'ru-one', 'few' => 'ru-few', 'other' => 'ru-few')
275+
end
276+
260277
it 'maps Hebrew to one/other — GlotPress is 2-form, so the CLDR dual is not represented' do
261278
data, missing = convert_po_to_stringsdict(
262279
simple_po(forms: ['פריט אחד', '%d פריטים'], plural: 'n != 1'),

0 commit comments

Comments
 (0)