Skip to content

Commit 24e6c53

Browse files
committed
fix: resolve in-app language switching issue in release build
1 parent 141daa9 commit 24e6c53

5 files changed

Lines changed: 111 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ with the `0.1.0` release.
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- Sample app: changing the app's language via **Settings → Apps → TapSense → Language** (the
12+
Android 13+ per-app language picker, wired by `android:localeConfig`/`locales_config.xml`)
13+
had no effect in the Play-installed release build (versionCode 5), while the same switch
14+
worked immediately in a sideloaded debug build. Root cause: an Android App Bundle splits
15+
resources by language into separate install-time delivery APKs by default, so a device only
16+
gets the one locale split matching its language at install time; Play is supposed to fetch the
17+
rest on demand when the in-app language is changed, but that on-demand delivery proved
18+
unreliable, silently leaving the app on its originally-installed locale. Confirmed via `aapt2
19+
dump` that every translated string and the `locales_config.xml` resource itself were fully
20+
intact in the release APK/AAB - shrinking/minification was never the cause. Fixed by setting
21+
`bundle { language { enableSplit = false } }` in `app/build.gradle.kts`, which is Android's own
22+
documented remedy for apps whose language can change independent of the system locale
23+
(https://developer.android.com/guide/app-bundle/configure-base): every install now packages
24+
all locale resources into the base module instead of relying on Play's on-demand split
25+
delivery. Verified by decoding the rebuilt `.aab`'s `BundleConfig.pb`, which now carries
26+
`SplitDimension{value: LANGUAGE, negate: true}`. Trade-off accepted: a small increase in
27+
download size for every install (translated strings only, no additional media/assets) in
28+
exchange for reliable in-app language switching.
29+
930
## [0.2.0] - 2026-09-02
1031

1132
### Changed

DECISIONS.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,28 @@ same set most apps cover in a first localization pass. Locale selection is Andro
8383
resource resolution: `values-<lang>/strings.xml` is picked automatically from the device's
8484
locale on every API level, no code required. `android:localeConfig` (`AndroidManifest.xml` +
8585
`res/xml/locales_config.xml`) additionally declares these locales for the Android 13+ per-app
86-
language picker and Play's per-locale APK splits - purely additive, ignored on minSdk 26 through
87-
32 (`tools:targetApi="33"` on the attribute documents this instead of suppressing the lint
88-
warning blindly). `app_name` is `translatable="false"`: a brand name, kept identical everywhere.
86+
language picker - purely additive, ignored on minSdk 26 through 32 (`tools:targetApi="33"` on the
87+
attribute documents this instead of suppressing the lint warning blindly). `app_name` is
88+
`translatable="false"`: a brand name, kept identical everywhere.
89+
90+
Play's per-locale App Bundle splitting is deliberately **disabled**
91+
(`bundle { language { enableSplit = false } }`, `app/build.gradle.kts`), not left on its default.
92+
The per-app language picker above lets a user select a language independent of the device's
93+
system locale - exactly the case Android's own App Bundle docs flag as needing this setting
94+
(https://developer.android.com/guide/app-bundle/configure-base): with splitting left on, Play
95+
installs only the one locale split matching the device's language at install time and is
96+
supposed to fetch the rest on demand when the in-app language changes, but that on-demand
97+
delivery proved unreliable in practice - a versionCode 5 closed tester found that switching
98+
languages via Settings → Apps → TapSense → Language did nothing on the Play-installed release,
99+
while the identical switch worked immediately on a sideloaded debug build (which, being a single
100+
monolithic APK, never depended on split delivery). Confirmed via `aapt2 dump` that the
101+
versionCode 5 release APK's resources, `locales_config.xml`, and every translation were fully
102+
intact - shrinking/minification was never the cause, only the App Bundle's language-split
103+
delivery was. Fixed in versionCode 6 by disabling the split, so every install now bundles every
104+
locale's resources in the base module instead of depending on Play to deliver the rest later.
105+
The trade-off is a small download-size increase for every install (translated strings only, no
106+
additional media) - accepted as worth it for reliable in-app language switching; this is a
107+
supported Gradle configuration choice, not a Play Store policy concern in either direction.
89108

90109
Arabic/Hebrew (RTL) is deliberately not included in this pass. Translating strings is safe and
91110
mechanically verifiable (lint's `MissingTranslation`/`ExtraTranslation`/`StringFormatMatches`

app/build.gradle.kts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ android {
2727
applicationId = "com.tapsense.app"
2828
minSdk = 26
2929
targetSdk = 36
30-
versionCode = 5
30+
versionCode = 6
3131
versionName = "1.0.0"
3232

3333
testInstrumentationRunner = "com.tapsense.app.HiltTestRunner"
@@ -90,6 +90,21 @@ android {
9090
}
9191
}
9292

93+
// Play's per-language APK-split delivery (the default for an App Bundle) only installs the
94+
// locale split matching the device's language at install time, then is supposed to fetch the
95+
// rest on demand when the user later changes the per-app language in Settings > Apps >
96+
// TapSense > Language. That on-demand fetch is unreliable in practice (confirmed on the
97+
// versionCode 5 closed test: switching language did nothing in the Play-installed release,
98+
// while a sideloaded debug APK - which always bundles every locale - worked immediately).
99+
// Disabling the split ships every locale's resources in the base module on every install, so
100+
// the zero-code resource resolution `locales_config.xml` documents always finds the strings
101+
// locally instead of depending on Play to deliver them.
102+
bundle {
103+
language {
104+
enableSplit = false
105+
}
106+
}
107+
93108
lint {
94109
warningsAsErrors = false
95110
abortOnError = true

docs/play-store/CHECKLIST.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ claim; anything not yet decided is marked **TODO (owner)**.
1717
`BuildConfig.VERSION_NAME`/Settings → About (`"1.0.0-debug"` vs. release's plain `"1.0.0"`)
1818
— so a tester's screenshot or a local dev's screen share is unambiguous about which build
1919
they're looking at.
20-
- [x] `versionCode`/`versionName` present (currently `5` / `"1.0.0"` — check `app/build.gradle.kts`
20+
- [x] `versionCode`/`versionName` present (currently `6` / `"1.0.0"` — check `app/build.gradle.kts`
2121
for the live values, since this line goes stale the moment either is bumped and isn't
2222
re-verified automatically). Convention for this repo: `versionCode` is a plain incrementing
2323
integer bumped for *every* build uploaded to any Play track (closed testing included) —
@@ -145,7 +145,20 @@ claim; anything not yet decided is marked **TODO (owner)**.
145145
Arabic/Hebrew (RTL) is deliberately deferred rather than shipped untested.
146146
- [x] Locale selection is automatic: Android resolves `values-<lang>/` from the device locale on
147147
every API level, no code required. `android:localeConfig` additionally wires the Android
148-
13+ per-app language picker and Play's per-locale APK splits (`res/xml/locales_config.xml`).
148+
13+ per-app language picker (`res/xml/locales_config.xml`).
149+
- [x] Play's per-locale App Bundle splits are **disabled**
150+
(`bundle { language { enableSplit = false } }` in `app/build.gradle.kts`), not left on the
151+
default. versionCode 5 shipped with the default (split) behavior, and a closed tester found
152+
that switching languages via Settings → Apps → TapSense → Language did nothing on the
153+
Play-installed build, because Play only installs the one locale split matching the device's
154+
language and its on-demand delivery of the rest proved unreliable — confirmed not a
155+
shrinking/minification issue via `aapt2 dump` on the versionCode 5 APK. Fixed in versionCode
156+
6 per Android's own documented guidance for apps with independent-of-system-locale language
157+
switching (https://developer.android.com/guide/app-bundle/configure-base); every install
158+
now bundles all locale resources, trading a small download-size increase for reliable
159+
switching. Not a Play policy concern either way — this is a supported Gradle configuration
160+
choice. See `CHANGELOG.md`'s `[Unreleased]` entry and `DECISIONS.md`'s "Localization"
161+
section.
149162
- [x] `./gradlew lint` reports zero `MissingTranslation`/`ExtraTranslation`/`StringFormatMatches`/
150163
`StringFormatCount` findings across all 8 locales in both modules — every translation key
151164
and format-argument count matches the English source exactly.

docs/play-store/RELEASE_NOTES.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,43 @@ heading:
1212
See [`CHECKLIST.md`](CHECKLIST.md) for the rest of the submission checklist and the
1313
versionCode/versionName convention this file follows.
1414

15+
## versionCode 6 — versionName 1.0.0 — Closed testing (2026-09-03)
16+
17+
### What's new (paste into Play Console)
18+
19+
```
20+
Fixed: changing the app's language in Settings > Apps > TapSense > Language now actually takes effect. Previously it silently did nothing for some testers.
21+
22+
No other changes in this build.
23+
```
24+
25+
(200 characters.)
26+
27+
### What actually changed (internal)
28+
29+
- **Fixed in-app language switching in the release build.** Reported by a closed tester on
30+
versionCode 5: picking a different language in Settings → Apps → TapSense → Language had no
31+
effect, even though the identical switch worked fine on a sideloaded debug build. Root cause:
32+
an Android App Bundle splits resources by language into separate install-time delivery APKs by
33+
default, so a device only receives the single locale split matching its language at install
34+
time — Play is supposed to fetch additional splits on demand when the per-app language changes,
35+
but that delivery proved unreliable, leaving the app stuck on whichever locale it was originally
36+
installed with. Confirmed via `aapt2 dump configurations`/`dump xmltree`/`dump resources` that
37+
the versionCode 5 release APK's `resources.arsc`, `locales_config.xml`, and every translated
38+
string were fully intact — resource shrinking/minification was never the cause, only the App
39+
Bundle's language-split delivery was. Fixed with `bundle { language { enableSplit = false } }`
40+
in `app/build.gradle.kts` — Android's own documented fix for apps whose language changes
41+
independent of the system locale
42+
(https://developer.android.com/guide/app-bundle/configure-base) — so every install now packages
43+
every locale's resources into the base module instead of depending on Play to deliver the rest
44+
later. Verified by decoding the rebuilt `.aab`'s `BundleConfig.pb`
45+
(`SplitDimension{value: LANGUAGE, negate: true}`) and re-confirming `assembleRelease`/
46+
`bundleRelease` both still succeed. Not a Play Store policy issue in either direction — disabling
47+
the split is a supported, policy-neutral Gradle configuration choice; the only trade-off is a
48+
small download-size increase (translated strings only) applied to every install rather than
49+
just non-English devices. See `CHANGELOG.md`'s `[Unreleased]` section and
50+
`DECISIONS.md`'s "Localization" section for full detail.
51+
1552
## versionCode 5 — versionName 1.0.0 — Closed testing (2026-09-03)
1653

1754
### What's new (paste into Play Console)

0 commit comments

Comments
 (0)