Skip to content

feature: offer the Android per-app language picker for localized apps - #369

Open
SRWieZ wants to merge 1 commit into
NativePHP:mainfrom
SRWieZ:feat/android-per-app-language-picker
Open

feature: offer the Android per-app language picker for localized apps#369
SRWieZ wants to merge 1 commit into
NativePHP:mainfrom
SRWieZ:feat/android-per-app-language-picker

Conversation

@SRWieZ

@SRWieZ SRWieZ commented Aug 24, 2026

Copy link
Copy Markdown
Member

The gap

Declare permission_localizations in config/nativephp.php and iOS registers the locales — per-locale InfoPlist.strings, knownRegions — so the app shows up in Settings → [App] → Preferred Language.

On Android, nothing happens. Android 13+ only lists an app in the per-app language picker when its manifest points at a locale-config resource, and the compiler never writes one. So a fully localized NativePHP app follows the system language but can never be switched per app:

Before After
No Language entry in App info Language entry appears

The fix

AndroidPluginCompiler now mirrors what the iOS compiler already does with the same declarations:

  1. Collects every locale declared by the app's permission_localizations and by plugins' ios.info_plist_localizations.
  2. Writes res/xml/locales_config.xml. A new documented default_locale config key (default 'en') leads the list, so users can always switch back to the app's base language explicitly.
  3. Injects android:localeConfig="@xml/locales_config" into the <application> tag (once — idempotent across rebuilds).

It runs before the no-plugins early return, so an app with only its own permission_localizations gets the picker too (same app-config-only path the iOS side handles).

Apps that declare no localizations are completely untouched — no file, no manifest change.

Verified on an emulator (Android 16)

The picker lists exactly the declared locales, and selecting one changes what the app's PHP sees (Locale.getDefault()Device::getInfo()['language']):

Picker App in French

Tests

Three new cases in AndroidCompilerTest: writes the config + manifest attribute from app declarations, merges plugin locales and stays idempotent, and leaves apps without localizations untouched. Full suite passes (899).

Android 13+ only lists an app in Settings' per-app language picker when
its manifest points at a locale-config resource. iOS already registers
the locales declared via permission_localizations (per-locale
InfoPlist.strings + knownRegions); Android silently ignored them, so a
localized NativePHP app followed the system language but never appeared
in per-app language settings.

The Android compiler now collects every locale declared by the app's
permission_localizations and plugins' ios.info_plist_localizations,
writes res/xml/locales_config.xml (new default_locale config leads the
list so users can always switch back), and injects
android:localeConfig into the <application> tag. Runs before the
no-plugins early return, mirrors the iOS app-config-only path, and is
idempotent. Apps declaring no localizations are untouched.
@SRWieZ
SRWieZ force-pushed the feat/android-per-app-language-picker branch from 49835ca to 8e28e8a Compare August 24, 2026 19:35
@SRWieZ SRWieZ changed the title Android: offer the per-app language picker for localized apps feature: offer the Android per-app language picker for localized apps Aug 24, 2026
@gwleuverink

Copy link
Copy Markdown
Contributor

Awesome, thanks! Great PR 🚀

One design question first, more for @simonhamp (and my own understanding) than for this PR. The language list comes from permission_localizations keys, including plugin ones. A plugin translating its permission explainer into ten languages would make an English-only app offer ten languages in the picker. iOS already does the same through the lproj folders, so this is consistent, but maybe the supported languages want their own key that both platforms read? Not sure on this one but it sounds like a bug 🤔

Then a smaller one, about what happens when the localizations go away again by an update after the app is already installed:

  1. Declare fr and nl under permission_localizations, run native:run android. locales_config.xml and the android:localeConfig attribute are written, the picker shows both languages.
  2. Set permission_localizations => [] (or remove the only plugin that shipped them) and run native:run android again.
  3. writeLocalesConfig returns early on the empty list, so the xml and the attribute stay exactly as they were.
  4. The new APK still carries localeConfig with fr and nl, and the picker keeps offering them to an app that no longer declares any.
  5. Only a fresh native:install clears the manifest, and since nativephp/ is gitignored a dev building releases from a long-lived scaffold can ship it that way.

Admittedly, this will likely not happen often in nativephp apps, but it might. It is cosmetic, but injectPluginProguardRules and injectGradlePlugins right above it do clear their block on an empty list. Was skipping that intentional, or would you be up for having this one strip the attribute and delete the file when the list is empty?

@SRWieZ

SRWieZ commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

I think this was originally based on iOS, if I recall correctly.

As a user, I’d personally prefer full control over which locales are suggested.

It really depends more on my Laravel app’s translation strings than on plugin permissions.

So maybe a new config ?

@gwleuverink

Copy link
Copy Markdown
Contributor

Exactly. We cant have a random plugin contributing a bunch of extra language options the app doesnt even support.

Dedicated config makes sense to me. Good to be explicit. What do you think bossmen @simonhamp @shanerbaner82 ?

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.

2 participants