Skip to content

feat(localization): add 3D map localization sample across Kotlin, Java, Compose and Snippets - #74

Merged
LoyalAbbas merged 3 commits into
mainfrom
feat/maps_localization
Oct 14, 2026
Merged

LoyalAbbas merged 3 commits into
mainfrom
feat/maps_localization

Conversation

@LoyalAbbas

Copy link
Copy Markdown
Collaborator

Adds the Maps Localization sample demonstrating dynamic 3D map language and region localization using LocaleOptions and GoogleMap3D.setLocale(LocaleOptions) across Kotlin Views, Java Views, Jetpack Compose, and Snippets.

Key Changes

  • Shared Clean Architecture Layer (:Maps3DSamples:ApiDemos:common):

    • Added LocalizationData.kt (MapLocalePreset, LocalizationState, DEFAULT_LOCALIZATION_CAMERA) with curated locale presets (en-US, zh-CN, fr-FR, ja-JP, es-ES, ar-SA, de-DE, and SYSTEM_DEFAULT) and toLocaleOptions() mapping.
    • Added LocalizationController.kt state machine and MapsLocalizationViewModel.kt exposing StateFlow (for Kotlin/Compose) and LiveData via asLiveData() (for Java Views).
    • Added shared control panel layout (control_panel_maps_localization.xml) and localized string resources (strings.xml).
  • 3-Way Feature Parity:

    • Kotlin Views (:Maps3DSamples:ApiDemos:kotlin-app): Added MapsLocalizationActivity.kt extending SampleBaseActivity and registered in MainActivity.kt / AndroidManifest.xml.
    • Java Views (:Maps3DSamples:ApiDemos:java-app): Added MapsLocalizationActivity.java extending SampleBaseActivity and registered in MainActivity.java / AndroidManifest.xml.
    • Jetpack Compose (:Maps3DSamples:ComposeDemos:app): Added MapsLocalizationActivity.kt with MapsLocalizationScreen and LocalizationControlCard composables, registered in MainActivity.kt / AndroidManifest.xml.
  • Documentation Snippets (:snippets:kotlin-app & :snippets:java-app):

    • Added MapLocalizationSnippets.kt and MapLocalizationSnippets.java with region tags (maps_android_3d_localization_*) demonstrating LocaleOptions and map.setLocale(localeOptions).
    • Added setLocale(LocaleOptions) pass-through to TrackedMap3D.kt and TrackedMap3D.java and registered the snippet group in SnippetRegistry.
  • Testing Suite:

    • Added Tier 2 & Tier 3 unit tests (LocalizationControllerTest.kt, MapsLocalizationViewModelTest.kt).
    • Added Tier 4 visual regression tests (MapsLocalizationVisualTest.kt, MapsLocalizationVisualTest.java).

Verification

  • Verified :Maps3DSamples:ApiDemos:common:testDebugUnitTest unit tests pass.
  • Verified Kotlin Views, Java Views, Jetpack Compose, and Snippets compile and format cleanly with spotlessApply.
  • Verified runtime language and region switching updates 3D map labels in-place without recreating Map3DView.

@LoyalAbbas
LoyalAbbas requested a review from dkhawk September 24, 2026 05:18
@LoyalAbbas
LoyalAbbas added this pull request to stack #75 September 24, 2026 05:18

@dkhawk dkhawk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary & Verification

LGTM! Excellent work on this feature.

100% 3-way language parity across Kotlin Views, Java Views, Jetpack Compose, and interactive Snippets is fully verified. We tested this build on a physical Pixel 6 (using the 16.0.0 RC artifact in local Maven) and confirmed that:

  1. Runtime Localization: Language and region switching via GoogleMap3D.setLocale(LocaleOptions) successfully updates 3D map tile labels in-place without reloading the scene or recreating Map3DView.
  2. Architecture & Tests: Clean separation of concerns (Data -> Controller -> ViewModel -> Views). All Tier 2 and Tier 3 unit tests (LocalizationControllerTest, MapsLocalizationViewModelTest) pass with 100% success rate.
  3. Snippet Region Tags: Interactive documentation samples in snippets/ strictly follow Google documentation region tags (maps_android_3d_localization_*).
  4. CI Status: The 4 failing CI jobs on GitHub Actions are an expected external artifact of the parent branch stack (#67) pending the public GMaven publication of play-services-maps3d:16.0.0.

Left two minor polish suggestions inline regarding dynamic SYSTEM_DEFAULT resolution and Compose effect keying. Approving the PR!

Comment on lines +91 to +96
SYSTEM_DEFAULT(
displayName = "System Default",
nativeLabel = SYSTEM_DEFAULT_CODE,
language = Locale.getDefault().language,
region = Locale.getDefault().country,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In MapLocalePreset.SYSTEM_DEFAULT, language and region are evaluated once at class initialization time as constructor arguments (Locale.getDefault().language / country). If a user changes their device system language in Android Settings while the app process is cached in memory, this enum constant will retain the stale initial locale.

Consider making language and region dynamic custom property accessors on MapLocalePreset so that selecting SYSTEM_DEFAULT always queries Locale.getDefault() dynamically:

// [START maps_android_3d_localization_preset_dynamic_locale]
enum class MapLocalePreset(
    val displayName: String,
    val nativeLabel: String,
    private val rawLanguage: String? = null,
    private val rawRegion: String? = null,
) {
    ENGLISH_US("English (US)", "en-US", "en", "US"),
    CHINESE_CHINA("Chinese (China)", "中文 (zh-CN)", "zh", "CN"),
    FRENCH_FRANCE("French (France)", "Français (fr-FR)", "fr", "FR"),
    JAPANESE_JAPAN("Japanese (Japan)", "日本語 (ja-JP)", "ja", "JP"),
    SPANISH_SPAIN("Spanish (Spain)", "Español (es-ES)", "es", "ES"),
    ARABIC_SAUDI_ARABIA("Arabic (Saudi Arabia)", "العربية (ar-SA)", "ar", "SA"),
    GERMAN_GERMANY("German (Germany)", "Deutsch (de-DE)", "de", "DE"),
    SYSTEM_DEFAULT("System Default", SYSTEM_DEFAULT_CODE);

    val language: String
        get() = rawLanguage ?: Locale.getDefault().language

    val region: String
        get() = rawRegion ?: Locale.getDefault().country
}
// [END maps_android_3d_localization_preset_dynamic_locale]

Comment on lines +132 to +134
LaunchedEffect(googleMap3DInstance, state.selectedPreset) {
googleMap3DInstance?.setLocale(state.toLocaleOptions())
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In MapsLocalizationScreen, LaunchedEffect is keyed on (googleMap3DInstance, state.selectedPreset). Keying on (googleMap3DInstance, state.languageCode, state.regionCode) (or state.toLocaleOptions()) directly decouples the effect from preset selection, ensuring that any custom locale modifications trigger the in-place map update even if selectedPreset does not change:

// [START maps_android_3d_localization_compose_sync_suggested]
    LaunchedEffect(googleMap3DInstance, state.languageCode, state.regionCode) {
        googleMap3DInstance?.setLocale(state.toLocaleOptions())
    }
// [END maps_android_3d_localization_compose_sync_suggested]

@LoyalAbbas
LoyalAbbas marked this pull request as ready for review October 13, 2026 12:45
@LoyalAbbas
LoyalAbbas force-pushed the feat/maps_localization branch from be9e88b to 6e9ccce Compare October 13, 2026 13:12
@LoyalAbbas
LoyalAbbas marked this pull request as draft October 13, 2026 13:42
@LoyalAbbas
LoyalAbbas marked this pull request as ready for review October 14, 2026 05:16

@kikoso kikoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you take a look at the comments, @LoyalAbbas ? Thanks! The CI failures are not related to GMaven: MainActivityTest fails, and the PlacesUIKit3D Spotless issue is already fixed on the base branch, so a rebase should take care of that one.

put(R.string.feature_title_roadmap_mode, RoadmapModeActivity.class);
put(R.string.feature_title_field_of_view, FieldOfViewActivity.class);
put(R.string.feature_title_projection_3d, Projection3DActivity.class);
put(R.string.feature_title_maps_localization, MapsLocalizationActivity.class);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes MainActivityTest.testSampleActivitiesContainsAllSamples fail, since it still expects hasSize(23). Could we bump it to 24 and add a contains(MapsLocalizationActivity.class) check like the other samples have?

modifier = Modifier.fillMaxSize(),
onMapReady = { map ->
googleMap3DInstance = map
map.setLocale(state.toLocaleOptions())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LaunchedEffect keyed on googleMap3DInstance already applies the locale as soon as the instance is set, so we end up calling setLocale twice on startup. Should we keep only one of them?

description = "Applies language='zh' and region='CN' via LocaleOptions and setLocale.",
)
fun setChineseLocalization() {
// [START maps_android_3d_localization_init_kt]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Region tags end up in the devsite docs and are painful to rename later. init and runtime don't say much next to japanese and default. What about maps_android_3d_localization_chinese_kt / _french_kt (and the same for Java)?


private fun showLanguageSelectionDialog() {
val presets = MapLocalePreset.entries
val labels = presets.map { "${it.displayName} — ${it.nativeLabel}" }.toTypedArray()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we move this label to a string resource, like we did with language_button_format? The same format is hardcoded in the Java and Compose versions too.

IconButton(onClick = onNavigateUp) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the back button has no contentDescription, so TalkBack will not announce it.

@LoyalAbbas
LoyalAbbas force-pushed the feat/maps_localization branch 2 times, most recently from 7d0bd3c to 0aefc3a Compare October 14, 2026 08:40

@kikoso kikoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for addressing the comments, @LoyalAbbas !

Base automatically changed from feat/upgrade_to_16.0.0 to main October 14, 2026 08:43
@LoyalAbbas
LoyalAbbas force-pushed the feat/maps_localization branch 2 times, most recently from 4b5b408 to 2f69ed7 Compare October 14, 2026 12:00
@LoyalAbbas
LoyalAbbas force-pushed the feat/maps_localization branch from 2f69ed7 to 0638de9 Compare October 14, 2026 12:16
@LoyalAbbas
LoyalAbbas merged commit 04bc6b9 into main Oct 14, 2026
12 checks passed
@LoyalAbbas
LoyalAbbas deleted the feat/maps_localization branch October 14, 2026 12:26
googlemaps-bot pushed a commit that referenced this pull request Oct 14, 2026
# [1.17.0](v1.16.0...v1.17.0) (2026-10-14)

### Features

* **localization:** add 3D map localization sample across Kotlin, Java, Compose and Snippets ([#74](#74)) ([04bc6b9](04bc6b9))
@googlemaps-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.17.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants