Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

250-change-language-fixes #275

Merged
merged 23 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
79650c0
remove old localization module, fix android + web
stefanschaller Aug 7, 2023
c919372
add first draft for iOS
stefanschaller Aug 8, 2023
724b44a
add first draft for iOS
stefanschaller Aug 8, 2023
0e36965
add ios draft
stefanschaller Aug 8, 2023
48a41d5
add example to test the localizations
stefanschaller Aug 9, 2023
b79f945
clean up and remove urls
stefanschaller Aug 9, 2023
fbac454
fix regex and add comment
stefanschaller Aug 9, 2023
9c27874
fix and improve web implementation
stefanschaller Aug 9, 2023
b678643
fix web impl and take case about non lists
stefanschaller Aug 9, 2023
e5dc2ba
adjust android impl and clean up web
stefanschaller Aug 9, 2023
652fda2
fix ios lang changing
JulianBissekkou Aug 9, 2023
6c5791e
move lang to extension
JulianBissekkou Aug 9, 2023
a54b722
move setLanguage into top level extension
stefanschaller Aug 9, 2023
a24bfd0
Merge remote-tracking branch 'origin/250-change-language-fixes' into …
stefanschaller Aug 9, 2023
ab93115
fix default language
stefanschaller Aug 10, 2023
5d5e46a
ignore layers where a "ref" is used
stefanschaller Aug 10, 2023
0ec1685
clean up code for checking if the current layer contains a language o…
stefanschaller Aug 11, 2023
19b4db9
clean up ios / swift code to check if there is an current language
stefanschaller Aug 11, 2023
de1d8ea
fix feedback from M0naco and language defaults to "name"
stefanschaller Aug 14, 2023
13d58bc
fix feedback from M0naco and language defaults to "name"
stefanschaller Aug 14, 2023
570ba41
fix feedback from M0naco and language defaults to "name"
stefanschaller Aug 14, 2023
1ccc9e9
fix linting issues
stefanschaller Aug 14, 2023
4720a29
add info to the readme / changleog
stefanschaller Aug 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ android {
dependencies {
implementation 'org.maplibre.gl:android-sdk:10.2.0'
implementation 'org.maplibre.gl:android-plugin-annotation-v9:2.0.0'
implementation 'org.maplibre.gl:android-plugin-localization-v9:2.0.0'
implementation 'org.maplibre.gl:android-plugin-offline-v9:2.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.plugins.localization.LocalizationPlugin;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.FillExtrusionLayer;
Expand All @@ -76,6 +75,7 @@
import com.mapbox.mapboxsdk.style.sources.ImageSource;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.mapboxsdk.style.sources.VectorSource;
import com.mapbox.mapboxsdk.style.types.Formatted;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
Expand All @@ -89,6 +89,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -131,7 +132,6 @@ final class MapboxMapController
private MethodChannel.Result mapReadyResult;
private LocationComponent locationComponent = null;
private LocationEngineCallback<LocationEngineResult> locationEngineCallback = null;
private LocalizationPlugin localizationPlugin;
private Style style;
private Feature draggedFeature;
private AndroidGesturesManager androidGesturesManager;
Expand Down Expand Up @@ -163,7 +163,6 @@ public void onStyleLoaded(@NonNull Style style) {

mapboxMap.addOnMapClickListener(MapboxMapController.this);
mapboxMap.addOnMapLongClickListener(MapboxMapController.this);
localizationPlugin = new LocalizationPlugin(mapView, mapboxMap, style);

methodChannel.invokeMethod("map#onStyleLoaded", null);
}
Expand Down Expand Up @@ -644,7 +643,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
case "map#matchMapLanguageWithDeviceDefault":
{
try {
localizationPlugin.matchMapLanguageWithDeviceDefault();
final Locale deviceLocale = Locale.getDefault();
MapboxMapUtils.setMapLanguage(mapboxMap, deviceLocale.getLanguage());

result.success(null);
} catch (RuntimeException exception) {
Log.d(TAG, exception.toString());
Expand Down Expand Up @@ -673,7 +674,8 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
{
final String language = call.argument("language");
try {
localizationPlugin.setMapLanguage(language);
MapboxMapUtils.setMapLanguage(mapboxMap, language);

result.success(null);
} catch (RuntimeException exception) {
Log.d(TAG, exception.toString());
Expand Down
30 changes: 30 additions & 0 deletions android/src/main/java/com/mapbox/mapboxgl/setMapLanguage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@file:JvmName("MapboxMapUtils")

package com.mapbox.mapboxgl

import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.style.expressions.Expression
import com.mapbox.mapboxsdk.style.layers.PropertyFactory
import com.mapbox.mapboxsdk.style.layers.SymbolLayer

fun MapboxMap.setMapLanguage(language: String) {
val layers = this.style?.layers ?: emptyList()

val languageRegex = Regex("(name:[a-z]+)")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I assume the idea here is to skip any layers with textfields that do not depend on the name from the tiles. But is also on purpose to ignore layers withtext = ["get", "name"]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We already ignore layers where ["get", "name"] is present, since the layer check for at least a : .

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, that's my question: is that on purpose?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not saying it's right or wrong, just occurred to me that I'm not sure whether a developer would expect such a layer to be localized or not...

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thinking about it more, i think there is a good argument for either approach:

  1. On the one hand, if the style author sets text = ["get", "name"] they may explicitly always want to show the non-translated name here --> we should ignore these layers
  2. On the other hand, if the developer explicitly calls this method to localize map labels, they might expect all map labels to be localized --> these layers should be localized as well

Copy link
Collaborator Author

@stefanschaller stefanschaller Aug 14, 2023

Choose a reason for hiding this comment

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

I would definitely go with option 1 because of multiple reasons:

  1. If the author just set ["get", "name"] or [ref], they want the plain name for sure.
  2. The developer should not care about which layer will be translated and which not, since the most developer are not familiar about the different layers and how the styling is working in detail.
  3. The setMapLanguage function would not work in our case, since we have a lot of usages of ["get", "name"] and [ref], and the text is just empty in that cases.


val symbolLayers = layers.filterIsInstance<SymbolLayer>()

for (layer in symbolLayers) {
// continue when there is no current expression
val expression = layer.textField.expression ?: continue

// We could skip the current iteration, whenever there is not current language.
if (!expression.toString().contains(languageRegex)) {
continue
}

val properties = "[\"coalesce\", [\"get\",\"name:$language\"],[\"get\",\"name:latin\"]]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would we maybe also want to also coalesce to just "name" as an ultimate fallback here?

Copy link
Collaborator

@m0nac0 m0nac0 Aug 11, 2023

Choose a reason for hiding this comment

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

I haven't tested it, but I think otherwise non-latin-names might be completely missing from the map (if there is no latin transliteration for the name in the tiles)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

name as final fallback sounds good in theory, but I'm not sure if something like that is working.


layer.setProperties(PropertyFactory.textField(Expression.raw(properties)))
}
}
78 changes: 78 additions & 0 deletions example/lib/localized_map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:maplibre_gl/mapbox_gl.dart';

import 'page.dart';

class LocalizedMapPage extends ExamplePage {
const LocalizedMapPage({super.key})
: super(const Icon(Icons.map), 'Localized screen map');

@override
Widget build(BuildContext context) {
return const LocalizedMap();
}
}

class LocalizedMap extends StatefulWidget {
const LocalizedMap({super.key});

@override
State createState() => LocalizedMapState();
}

class LocalizedMapState extends State<LocalizedMap> {
final _mapReadyCompleter = Completer<MaplibreMapController>();

var _mapLanguage = "en";

@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButton<String>(
value: _mapLanguage,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
onChanged: (value) {
if (value == null) return;

setState(() => _mapLanguage = value);
_setMapLanguage();
},
items: ["en", "de", "es", "pl"]
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Expanded(
child: MaplibreMap(
onMapCreated: _onMapCreated,
initialCameraPosition:
const CameraPosition(target: LatLng(0.0, 0.0)),
onStyleLoadedCallback: _onStyleLoadedCallback,
),
),
],
),
);
}

void _onMapCreated(MaplibreMapController controller) {
_mapReadyCompleter.complete(controller);
}

void _onStyleLoadedCallback() {
_setMapLanguage();
}

Future<void> _setMapLanguage() async {
final controller = await _mapReadyCompleter.future;
controller.setMapLanguage(_mapLanguage);
}
}
2 changes: 2 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:location/location.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:maplibre_gl_example/get_map_informations.dart';
import 'package:maplibre_gl_example/given_bounds.dart';
import 'package:maplibre_gl_example/localized_map.dart';
import 'package:maplibre_gl_example/no_location_permission_page.dart';

import 'animate_camera.dart';
Expand Down Expand Up @@ -37,6 +38,7 @@ import 'package:maplibre_gl/mapbox_gl.dart';
final List<ExamplePage> _allPages = <ExamplePage>[
const MapUiPage(),
const FullMapPage(),
const LocalizedMapPage(),
const AnimateCameraPage(),
const MoveCameraPage(),
const PlaceSymbolPage(),
Expand Down
55 changes: 55 additions & 0 deletions ios/Classes/MGLMapView+setLanguage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// MLNMapView+setLanguage.swift
// maplibre_gl
//
// Created by Julian Bissekkou on 09.08.23.
//

import Foundation
import Mapbox

extension MGLMapView {
func setMapLanguage(_ language: String) {
guard let style = style else { return }

let layers = style.layers

for layer in layers {
if let symbolLayer = layer as? MGLSymbolStyleLayer {
if symbolLayer.text == nil {
continue
}

// We could skip the current iteration, whenever there is not current language.
if !symbolLayer.text.description.containsLanguage() {
continue
}

let properties = ["text-field": "[\"coalesce\", [\"get\",\"name:\(language)\"],[\"get\",\"name:latin\"]]"]

LayerPropertyConverter.addSymbolProperties(
symbolLayer: symbolLayer,
properties: properties
)
}
}
}
}


private extension String {
func containsLanguage() -> Bool {
do {
let regex = try NSRegularExpression(pattern: "(name:[a-z]+)")
let range = NSRange(location: 0, length: self.utf16.count)

if let _ = regex.firstMatch(in: self, options: [], range: range) {
return true
} else {
return false
}
} catch {
return false
}
}
}
14 changes: 9 additions & 5 deletions ios/Classes/MapboxMapController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,10 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma
}
result(nil)
case "map#matchMapLanguageWithDeviceDefault":
if let style = mapView.style {
style.localizeLabels(into: nil)
if let langStr = Locale.current.languageCode {
setMapLanguage(language: langStr)
}

result(nil)
case "map#updateContentInsets":
guard let arguments = methodCall.arguments as? [String: Any] else { return }
Expand Down Expand Up @@ -195,9 +196,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma
}
case "map#setMapLanguage":
guard let arguments = methodCall.arguments as? [String: Any] else { return }
if let localIdentifier = arguments["language"] as? String, let style = mapView.style {
let locale = Locale(identifier: localIdentifier)
style.localizeLabels(into: locale)
if let localIdentifier = arguments["language"] as? String {
setMapLanguage(language: localIdentifier)
}
result(nil)
case "map#queryRenderedFeatures":
Expand Down Expand Up @@ -841,6 +841,10 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma
private func getCamera() -> MGLMapCamera? {
return trackCameraPosition ? mapView.camera : nil
}

private func setMapLanguage(language: String) {
self.mapView.setMapLanguage(language)
}

/*
* Scan layers from top to bottom and return the first matching feature
Expand Down
2 changes: 2 additions & 0 deletions maplibre_gl_web/lib/mapbox_gl_web.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
library maplibre_gl_web;

import 'dart:async';
import 'dart:convert';
import 'dart:developer';
// FIXED HERE: https://github.com/dart-lang/linter/pull/1985
// ignore_for_file: avoid_web_libraries_in_flutter
import 'dart:html';
Expand Down
31 changes: 26 additions & 5 deletions maplibre_gl_web/lib/src/mapbox_web_gl_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,32 @@ class MaplibreMapController extends MapLibreGlPlatform

@override
Future<void> setMapLanguage(String language) async {
_map.setLayoutProperty(
'country-label',
'text-field',
['get', 'name_' + language],
);
final List<dynamic> layers = _map.getStyle()?.layers ?? [];

final languageRegex = RegExp("(name:[a-z]+)");

final symbolLayers = layers.where((layer) => layer.type == "symbol");

for (final layer in symbolLayers) {
final dynamic properties = _map.getLayoutProperty(layer.id, 'text-field');

if (properties == null) {
continue;
}

// We could skip the current iteration, whenever there is not current language.
if (!languageRegex.hasMatch(properties.toString())) {
continue;
}

final newProperties = [
"coalesce",
["get", "name:$language"],
["get", "name:latin"],
];

_map.setLayoutProperty(layer.id, 'text-field', newProperties);
}
}

@override
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ dependencies:
git:
url: https://github.com/maplibre/flutter-maplibre-gl.git
path: maplibre_gl_platform_interface
ref: main
ref: 250-change-language-fixes
maplibre_gl_web:
git:
url: https://github.com/maplibre/flutter-maplibre-gl.git
path: maplibre_gl_web
ref: main
ref: 250-change-language-fixes

dependency_overrides:
maplibre_gl_platform_interface:
Expand Down
Loading