Skip to content

push: localize the destination errors the settings screen shows - #1700

Merged
ryanbr merged 1 commit into
mainfrom
push-followup-i18n
Aug 28, 2026
Merged

ryanbr merged 1 commit into
mainfrom
push-followup-i18n

Conversation

@ryanbr

@ryanbr ryanbr commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Follow-up to the self-hosted push feature, covering the two gaps noted when it merged plus one thing the merge made visible.

Ten strings shipped as English in every locale

Nine PushEndpointPolicy refusals were rendered straight into a Text() under the endpoint field, and the scheduler's enqueue failure reached the same screen via lastError — inside an otherwise localized frame, which is the tell.

The i18n gate passed on all ten. Tools/i18n_audit.py only sees literals inside @Composable functions, and PushEndpointPolicy is a plain object. That blind spot has now hidden two separate regressions, so it is worth stating plainly rather than treating this as a one-off.

The seam already existed: pushFailureMessage resolves the entire transport failure taxonomy against R.string.push_error_*. These two paths just weren't using it. So Result.Invalid carries a Problem code instead of a sentence, and pushEndpointProblemMessage resolves it next to its sibling — which keeps the policy object free of Android types, the reason it held sentences in the first place.

A new test pins each rejected URL shape to its own code. The existing tests assert is Invalid, which cannot catch a shape mapping to the wrong message — the user would see wording that contradicts what they typed.

requiresLocalAddress removed

It was set during validation and asserted once in a test, but nothing ever read it. A field named like that reads as a runtime guard, and there wasn't one.

The cleartext rule is enforced where it actually holds: validate() refuses plain HTTP unless the host is a numeric literal resolving to a local or private address. That is also why there is no DNS left to rebind, so the field had nothing to add.

Privacy doc section order

The update check was documented as a #### heading placed ahead of 1.1a. Once push landed as 1.1d the file read 1.1c, 1.1a, 1.1b, 1.1d. Moved into sequence and levelled to ###. No wording changed — this is placement only.

Verified

testFullDebugUnitTest with --rerun-tasks --no-build-cache: 4,709 tests, 0 failures, and PushEndpointPolicyTest confirmed at 9 cases including the new one. Tools/i18n_audit.py --ci main clean. All seven strings.xml parse and each carries all ten keys.

Not covered: lintVitalFullRelease needs a keystore, so it is left to CI.

Follow-up to #1631. Ten user-facing strings shipped as hardcoded English and rendered in
every locale: the nine PushEndpointPolicy refusals, drawn straight into a Text() at the
endpoint field, and the scheduler's enqueue failure, which reaches the same screen through
lastError inside an otherwise localized frame.

The i18n gate passed on all of them. Tools/i18n_audit.py only sees literals inside
@composable functions, and PushEndpointPolicy is a plain object — the same blind spot that
has now hidden two separate regressions.

The seam already existed. pushFailureMessage resolves the whole transport failure taxonomy
against R.string.push_error_*; these two paths simply did not use it. So Result.Invalid now
carries a Problem code instead of a sentence, pushEndpointProblemMessage resolves it beside
its sibling, and the policy object keeps its freedom from Android types. A new test pins
each rejected URL shape to its own code, since a shape quietly mapping to the wrong one
would show wording that contradicts what the user typed.

Also drops ValidEndpoint.requiresLocalAddress. It was set at validation and asserted once in
a test, but nothing ever read it, so it read as a runtime guard that does not exist. The
cleartext rule is enforced where it actually holds: validate() refuses plain HTTP unless the
host is a numeric literal that resolves to a local or private address, which is also why
there is no DNS left to rebind.

Finally, the update check was documented as a #### heading placed ahead of 1.1a, so after
push landed as 1.1d the privacy doc read 1.1c, 1.1a, 1.1b, 1.1d. Moved into sequence and
levelled to ###. No wording changed.

Translations for all six locales. Verified: unit suite green, Tools/i18n_audit.py --ci clean.
@ryanbr
ryanbr merged commit e2dea38 into main Aug 28, 2026
4 checks passed
@ryanbr
ryanbr deleted the push-followup-i18n branch August 28, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant