From 401b6692110d0fa719d95c8cf46ed77fd96b3625 Mon Sep 17 00:00:00 2001 From: Langston Smith Date: Wed, 21 Feb 2018 15:45:06 -0800 Subject: [PATCH] Localization plugin (#74) * Basic map localization support added * fixed missing junit dependency * use regrex to replace only the part needing change --- Makefile | 1 + app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 12 + .../activity/LocalizationActivity.java | 182 ++++++++++ app/src/main/res/drawable/ic_camera.xml | 12 + .../res/drawable/ic_translate_white_24dp.xml | 9 + .../main/res/layout/activity_localization.xml | 65 ++++ app/src/main/res/menu/menu_languages.xml | 52 +++ app/src/main/res/values/strings.xml | 8 +- plugin-localization/CHANGELOG.md | 7 + plugin-localization/README.md | 3 + plugin-localization/build.gradle | 36 ++ plugin-localization/gradle.properties | 6 + plugin-localization/javadoc.gradle | 17 + .../src/main/AndroidManifest.xml | 2 + .../LocalizationPlugin.java | 206 +++++++++++ .../MapLocale.java | 342 ++++++++++++++++++ .../package-info.java | 6 + .../localization/LocalizationPluginTest.java | 43 +++ .../plugins/localization/MapLocaleTest.java | 28 ++ settings.gradle | 3 +- 21 files changed, 1039 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/mapbox/mapboxsdk/plugins/testapp/activity/LocalizationActivity.java create mode 100644 app/src/main/res/drawable/ic_camera.xml create mode 100644 app/src/main/res/drawable/ic_translate_white_24dp.xml create mode 100644 app/src/main/res/layout/activity_localization.xml create mode 100644 app/src/main/res/menu/menu_languages.xml create mode 100644 plugin-localization/CHANGELOG.md create mode 100644 plugin-localization/README.md create mode 100644 plugin-localization/build.gradle create mode 100644 plugin-localization/gradle.properties create mode 100644 plugin-localization/javadoc.gradle create mode 100644 plugin-localization/src/main/AndroidManifest.xml create mode 100644 plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/LocalizationPlugin.java create mode 100644 plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/MapLocale.java create mode 100644 plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/package-info.java create mode 100644 plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/LocalizationPluginTest.java create mode 100644 plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/MapLocaleTest.java diff --git a/Makefile b/Makefile index 09e752799..20bd6a514 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ MBGL_ANDROID_PLUGINS += building;plugin-building MBGL_ANDROID_PLUGINS += cluster;plugin-cluster MBGL_ANDROID_PLUGINS += offline;plugin-offline MBGL_ANDROID_PLUGINS += places;plugin-places +MBGL_ANDROID_PLUGINS += localization;plugin-localization sonarqube: ./gradlew test diff --git a/app/build.gradle b/app/build.gradle index 2584e4cc4..c87ace51d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -94,6 +94,7 @@ dependencies { implementation project(':plugin-cluster') implementation project(':plugin-places') implementation project(':plugin-offline') + implementation project(':plugin-localization') } sonarqube { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4d9cf35e9..6d3a648e9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -171,6 +171,18 @@ android:value=".activity.FeatureOverviewActivity"/> + + + + + + + + diff --git a/app/src/main/res/drawable/ic_translate_white_24dp.xml b/app/src/main/res/drawable/ic_translate_white_24dp.xml new file mode 100644 index 000000000..9ff750038 --- /dev/null +++ b/app/src/main/res/drawable/ic_translate_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_localization.xml b/app/src/main/res/layout/activity_localization.xml new file mode 100644 index 000000000..42f434da8 --- /dev/null +++ b/app/src/main/res/layout/activity_localization.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_languages.xml b/app/src/main/res/menu/menu_languages.xml new file mode 100644 index 000000000..a9f780f63 --- /dev/null +++ b/app/src/main/res/menu/menu_languages.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fda23135..f5184f63b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Map Plugins + Map Plugins category @@ -10,6 +10,7 @@ Annotations Places Offline + Localization Traffic Plugin @@ -25,6 +26,7 @@ Create region List regions Place picker + Localization Plugin Add Traffic layers to any Mapbox basemap. @@ -40,6 +42,7 @@ Use a form to create an offline region. List all offline regions. Launch the place picker activity and receive result + Automatically localize the map labels into the device\'s set language. None @@ -50,6 +53,9 @@ Min zoom: %1$d Max zoom: %1$d + Make sure that the device\'s default language is not English + Map not localized to device language and now set to French + Map localized to device language Example shows how to launch the Place Picker using the Floating action button and receiving a result in onActivityResult. diff --git a/plugin-localization/CHANGELOG.md b/plugin-localization/CHANGELOG.md new file mode 100644 index 000000000..9e525736f --- /dev/null +++ b/plugin-localization/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog for the Mapbox Localization Plugin + +Mapbox welcomes participation and contributions from everyone. + +### 0.1.0 - TBD + +- Initial release as a standalone package. diff --git a/plugin-localization/README.md b/plugin-localization/README.md new file mode 100644 index 000000000..123b8b2e1 --- /dev/null +++ b/plugin-localization/README.md @@ -0,0 +1,3 @@ +# Mapbox Localization Plugin + +TODO \ No newline at end of file diff --git a/plugin-localization/build.gradle b/plugin-localization/build.gradle new file mode 100644 index 000000000..b21beaf91 --- /dev/null +++ b/plugin-localization/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion androidVersions.compileSdkVersion + buildToolsVersion androidVersions.buildToolsVersion + + defaultConfig { + minSdkVersion androidVersions.minSdkVersion + targetSdkVersion androidVersions.targetSdkVersion + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + configurations { + javadocDeps + } + + lintOptions { + abortOnError false + } +} + +dependencies { + implementation dependenciesList.supportAppcompatV7 + implementation dependenciesList.mapboxMapSdk + implementation dependenciesList.timber + javadocDeps dependenciesList.mapboxMapSdk + + // Unit testing + testImplementation dependenciesList.junit + testImplementation dependenciesList.mockito +} + +apply from: 'javadoc.gradle' +apply from: "${rootDir}/gradle/mvn-push-android.gradle" +apply from: "${rootDir}/gradle/checkstyle.gradle" +apply from: "${rootDir}/gradle/jacoco.gradle" \ No newline at end of file diff --git a/plugin-localization/gradle.properties b/plugin-localization/gradle.properties new file mode 100644 index 000000000..39fedd353 --- /dev/null +++ b/plugin-localization/gradle.properties @@ -0,0 +1,6 @@ +VERSION_NAME=0.1.0-SNAPSHOT +POM_ARTIFACT_ID=mapbox-android-plugin-localization +POM_NAME=Mapbox Android Localization Plugin +POM_DESCRIPTION=Mapbox Android Localization Plugin +POM_PACKAGING=aar + diff --git a/plugin-localization/javadoc.gradle b/plugin-localization/javadoc.gradle new file mode 100644 index 000000000..e401fa558 --- /dev/null +++ b/plugin-localization/javadoc.gradle @@ -0,0 +1,17 @@ +android.libraryVariants.all { variant -> + def name = variant.name + task "javadoc$name"(type: Javadoc) { + description = "Generates javadoc for build $name" + failOnError = false + destinationDir = new File(destinationDir, variant.baseName) + source = files(variant.javaCompile.source) + classpath = files(variant.javaCompile.classpath.files) + files(android.bootClasspath) + configurations.javadocDeps + options.windowTitle("Mapbox Android Plugins $VERSION_NAME Reference") + options.docTitle("Mapbox Android Plugins $VERSION_NAME") + options.header("Mapbox Android Plugins $VERSION_NAME Reference") + options.bottom("© 2017 Mapbox. All rights reserved.") + options.links("http://docs.oracle.com/javase/7/docs/api/") + options.linksOffline("http://d.android.com/reference/", "$System.env.ANDROID_HOME/docs/reference") + exclude '**/R.java', '**/BuildConfig.java' + } +} \ No newline at end of file diff --git a/plugin-localization/src/main/AndroidManifest.xml b/plugin-localization/src/main/AndroidManifest.xml new file mode 100644 index 000000000..060257cfa --- /dev/null +++ b/plugin-localization/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/LocalizationPlugin.java b/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/LocalizationPlugin.java new file mode 100644 index 000000000..02fa514ef --- /dev/null +++ b/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/LocalizationPlugin.java @@ -0,0 +1,206 @@ +package com.mapbox.mapboxsdk.plugins.localization; + +import android.support.annotation.NonNull; + +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.plugins.localization.MapLocale.Languages; +import com.mapbox.mapboxsdk.style.layers.Layer; +import com.mapbox.mapboxsdk.style.layers.SymbolLayer; +import com.mapbox.mapboxsdk.style.sources.Source; +import com.mapbox.mapboxsdk.style.sources.VectorSource; + +import java.util.List; +import java.util.Locale; + +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField; + +/** + * Useful class for quickly adjusting the maps language and the maps camera starting position. + * You can either use {@link #matchMapLanguageWithDeviceDefault()} to match the map language with + * the one being currently used on the device. Using {@link #setMapLanguage(Locale)} and it's + * variants, you can also change the maps langauge at anytime to any of the supported languages. + * + * @since 0.1.0 + */ +public final class LocalizationPlugin implements MapView.OnMapChangedListener { + + private final MapboxMap mapboxMap; + private MapLocale mapLocale; + + /** + * Public constructor for passing in the required {@link MapboxMap} object. + * + * @param mapboxMap the Mapbox map object which your current map view is using for control + * @since 0.1.0 + */ + public LocalizationPlugin(@NonNull MapView mapview, @NonNull MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + mapview.addOnMapChangedListener(this); + } + + /** + * Handles resetting the map language when the map style changes. + */ + @Override + public void onMapChanged(int change) { + if (change == MapView.DID_FINISH_LOADING_STYLE && mapLocale != null) { + setMapLanguage(mapLocale); + } + } + + /* + * Map languages + */ + + /** + * Initializing this class and then calling this method oftentimes will be the only thing you'll + * need to quickly adjust the map language to the devices specified language. + * + * @since 0.1.0 + */ + public void matchMapLanguageWithDeviceDefault() { + setMapLanguage(Locale.getDefault()); + } + + /** + * Set the map language directly by using one of the supported map languages found in + * {@link Languages}. + * + * @param language one of the support languages Mapbox uses + * @since 0.1.0 + */ + public void setMapLanguage(@Languages String language) { + setMapLanguage(new MapLocale(language)); + } + + /** + * If you'd like to set the map language to a specific locale, you can pass it in as a parameter + * and MapLocale will try matching the information with one of the MapLocales found in it's map. + * If one isn't found, a null point exception will be thrown. To prevent this, ensure that the + * locale you are trying to use have a complementary {@link MapLocale} for it. + * + * @param locale a {@link Locale} which has a complementary {@link MapLocale} for it + * @throws NullPointerException thrown when the locale passed into the method doesn't have a + * matching {@link MapLocale} + * @since 0.1.0 + */ + public void setMapLanguage(@NonNull Locale locale) { + setMapLanguage(checkMapLocalNonNull(locale)); + } + + /** + * You can pass in a {@link MapLocale} directly into this method which uses the language defined + * in it to represent the language found on the map. + * + * @param mapLocale the {@link MapLocale} object which contains the desired map language + * @since 0.1.0 + */ + public void setMapLanguage(@NonNull MapLocale mapLocale) { + this.mapLocale = mapLocale; + List layers = mapboxMap.getLayers(); + for (Source source : mapboxMap.getSources()) { + if (sourceIsFromMapbox(source)) { + for (Layer layer : layers) { + if (layerHasAdjustableTextField(layer)) { + String textField = ((SymbolLayer) layer).getTextField().getValue(); + if (textField != null + && (textField.contains("{name") || textField.contains("{abbr}"))) { + textField = textField.replaceAll("[{]((name).*?)[}]", + String.format("{%s}", mapLocale.getMapLanguage())); + layer.setProperties(textField(textField)); + } + } + } + } + } + } + + /* + * Camera bounding box + */ + + /** + * Adjust the map's camera position so that the entire countries boarders are within the viewport. + * Specifically, this method gets the devices currently set locale and adjust the map camera to + * view that country if a {@link MapLocale]} matches. + * + * @since 0.1.0 + */ + public void setCameraToLocaleCountry() { + setCameraToLocaleCountry(Locale.getDefault()); + } + + /** + * If you'd like to manually set the camera position to a specific map region or country, pass in + * the locale (which must have a paired }{@link MapLocale}) to work properly + * + * @param locale a {@link Locale} which has a complementary {@link MapLocale} for it + * @throws NullPointerException thrown when the locale passed into the method doesn't have a + * matching {@link MapLocale} + * @since 0.1.0 + */ + public void setCameraToLocaleCountry(Locale locale) { + setCameraToLocaleCountry(checkMapLocalNonNull(locale)); + } + + /** + * You can pass in a {@link MapLocale} directly into this method which uses the country bounds + * defined in it to represent the language found on the map. + * + * @param mapLocale he {@link MapLocale} object which contains the desired map bounds + * @throws NullPointerException thrown when it was expecting a {@link LatLngBounds} but instead + * it was null + * @since 0.1.0 + */ + public void setCameraToLocaleCountry(MapLocale mapLocale) { + LatLngBounds bounds = mapLocale.getCountryBounds(); + if (bounds == null) { + throw new NullPointerException("Expected a LatLngBounds object but received null instead. Mak" + + "e sure your MapLocale instance also has a country bounding box defined."); + } + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50)); + } + + /* + * Supporting methods + */ + + private MapLocale checkMapLocalNonNull(Locale locale) { + MapLocale mapLocale = MapLocale.getMapLocale(locale); + if (mapLocale == null) { + throw new NullPointerException("Locale " + locale.toString() + " has no matching MapLocale ob" + + "ject. You need to create an instance of MapLocale and add it to the MapLocale Cache usin" + + "g the addMapLocale method."); + } + return mapLocale; + } + + /** + * Checks whether the map's source is a source provided by Mapbox, rather than a custom source. + * + * @param singleSource an individual source object from the map + * @return true if the source is from the Mapbox Streets vector source, false if it's not. + */ + private boolean sourceIsFromMapbox(Source singleSource) { + return singleSource instanceof VectorSource + && ((VectorSource) singleSource).getUrl().substring(0, 9).equals("mapbox://") + && (((VectorSource) singleSource).getUrl().contains("mapbox.mapbox-streets-v7") + || ((VectorSource) singleSource).getUrl().contains("mapbox.mapbox-streets-v6")); + } + + /** + * Checks whether a single map layer has a textField that could potentially be localized to the + * device's language. + * + * @param singleLayer an individual layer from the map + * @return true if the layer has a textField eligible for translation, false if not. + */ + private boolean layerHasAdjustableTextField(Layer singleLayer) { + return singleLayer instanceof SymbolLayer && (((SymbolLayer) singleLayer).getTextField() != null + && (((SymbolLayer) singleLayer).getTextField().getValue() != null + && !(((SymbolLayer) singleLayer).getTextField().getValue().isEmpty()))); + } +} \ No newline at end of file diff --git a/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/MapLocale.java b/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/MapLocale.java new file mode 100644 index 000000000..83906d897 --- /dev/null +++ b/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/MapLocale.java @@ -0,0 +1,342 @@ +package com.mapbox.mapboxsdk.plugins.localization; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringDef; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; + +import java.lang.annotation.Retention; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * A {@link MapLocale} object builds off of the {@link Locale} object and provides additional + * geographical information particular to the Mapbox Map SDK. Like Locale, MapLocale can be used to + * make the map locale sensitive. + *

+ * The {@link MapLocale} object can be used to aquire the matching Locales map language; useful for + * translating the map language into one of the supported ones found in {@link Languages}. + *

+ * You'll also be able to get bounding box information for that same country so the map starting + * position target can adjust itself over the devices locale country. + *

+ * A handful of {@link MapLocale}'s are already constructed and offered through this class as static + * variables. If a country is missing and you'd like to add it, you can use one of the + * {@link MapLocale} constructors to build a valid map locale. Once this is done, you need to add it + * to the locale cache using {@link MapLocale#addMapLocale(Locale, MapLocale)} were the first + * parameter is the {@link Locale} object which matches up with your newly created + * {@link MapLocale}. + * + * @since 0.1.0 + */ +public final class MapLocale { + + /* + * Supported Mapbox map languages. + */ + + /** + * the name (or names) used locally for the place. + */ + public static final String LOCAL_NAME = "name"; + + /** + * English (if available, otherwise same as name) + */ + public static final String ENGLISH = "name_en"; + + /** + * French (if available, otherwise same as name_en) + */ + public static final String FRENCH = "name_fr"; + + /** + * Arabic (if available, otherwise same as name) + */ + public static final String ARABIC = "name_ar"; + + /** + * Spanish (if available, otherwise same as name_en) + */ + public static final String SPANISH = "name_es"; + + /** + * German (if available, otherwise same as name_en) + */ + public static final String GERMAN = "name_de"; + + /** + * Portuguese (if available, otherwise same as name_en) + */ + public static final String PORTUGUESE = "name_pt"; + + /** + * Russian (if available, otherwise same as name) + */ + public static final String RUSSIAN = "name_ru"; + + /** + * Chinese (if available, otherwise same as name) + */ + public static final String CHINESE = "name_zh"; + + /** + * Simplified Chinese (if available, otherwise same as name) + */ + public static final String SIMPLIFIED_CHINESE = "name_zh-Hans"; + + @Retention(SOURCE) + @StringDef( {LOCAL_NAME, ENGLISH, FRENCH, SIMPLIFIED_CHINESE, ARABIC, SPANISH, GERMAN, PORTUGUESE, + RUSSIAN, CHINESE}) + public @interface Languages { + } + + /* + * Some Country Bounding Boxes used for the default provided MapLocales. + */ + + /** + * USA Bounding box excluding Hawaii and Alaska extracted from Open Street Map + */ + static final LatLngBounds USA_BBOX = new LatLngBounds.Builder() + .include(new LatLng(49.388611, -124.733253)) + .include(new LatLng(24.544245, -66.954811)).build(); + + /** + * UK Bounding Box extracted from Open Street Map + */ + static final LatLngBounds UK_BBOX = new LatLngBounds.Builder() + .include(new LatLng(59.360249, -8.623555)) + .include(new LatLng(49.906193, 1.759)).build(); + + /** + * Canada Bounding Box extracted from Open Street Map + */ + static final LatLngBounds CANADA_BBOX = new LatLngBounds.Builder() + .include(new LatLng(83.110626, -141.0)) + .include(new LatLng(41.67598, -52.636291)).build(); + + /** + * China Bounding Box extracted from Open Street Map + */ + static final LatLngBounds CHINA_BBOX = new LatLngBounds.Builder() + .include(new LatLng(53.56086, 73.557693)) + .include(new LatLng(15.775416, 134.773911)).build(); + + /** + * Germany Bounding Box extracted from Open Street Map + */ + static final LatLngBounds GERMANY_BBOX = new LatLngBounds.Builder() + .include(new LatLng(55.055637, 5.865639)) + .include(new LatLng(47.275776, 15.039889)).build(); + + /** + * Korea Bounding Box extracted from Open Street Map + */ + static final LatLngBounds KOREA_BBOX = new LatLngBounds.Builder() + .include(new LatLng(38.612446, 125.887108)) + .include(new LatLng(33.190945, 129.584671)).build(); + + /** + * Japan Bounding Box extracted from Open Street Map + */ + static final LatLngBounds JAPAN_BBOX = new LatLngBounds.Builder() + .include(new LatLng(45.52314, 122.93853)) + .include(new LatLng(24.249472, 145.820892)).build(); + + /** + * France Bounding Box extracted from Open Street Map + */ + static final LatLngBounds FRANCE_BBOX = new LatLngBounds.Builder() + .include(new LatLng(51.092804, -5.142222)) + .include(new LatLng(41.371582, 9.561556)).build(); + + /** + * Italy Bounding Box extracted from Open Street Map + */ + static final LatLngBounds ITALY_BBOX = new LatLngBounds.Builder() + .include(new LatLng(47.095196, 6.614889)) + .include(new LatLng(36.652779, 18.513445)).build(); + + /** + * Peoples Republic of China Bounding Box extracted from Open Street Map + */ + static final LatLngBounds PRC_BBOX = new LatLngBounds.Builder() + .include(new LatLng(53.56086, 73.557693)) + .include(new LatLng(15.775416, 134.773911)).build(); + + /* + * Some MapLocales already defined (these match with the predefined ones in the Locale class) + */ + + /** + * Useful constant for country. + */ + public static final MapLocale FRANCE = new MapLocale(FRENCH, FRANCE_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale GERMANY = new MapLocale(GERMAN, GERMANY_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale ITALY = new MapLocale(LOCAL_NAME, ITALY_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale JAPAN = new MapLocale(LOCAL_NAME, JAPAN_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale KOREA = new MapLocale(LOCAL_NAME, KOREA_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale CHINA = new MapLocale(SIMPLIFIED_CHINESE, CHINA_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale PRC = new MapLocale(SIMPLIFIED_CHINESE, PRC_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale UK = new MapLocale(ENGLISH, UK_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale US = new MapLocale(ENGLISH, USA_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale CANADA = new MapLocale(ENGLISH, CANADA_BBOX); + + /** + * Useful constant for country. + */ + public static final MapLocale CANADA_FRENCH = new MapLocale(FRENCH, CANADA_BBOX); + + /** + * Maps out the Matching pair of {@link Locale} and {@link MapLocale}. In other words, if I have a + * {@link Locale#CANADA}, this should be matched up with {@link MapLocale#CANADA}. + */ + private static final Map LOCALE_SET; + + static { + LOCALE_SET = new HashMap<>(); + LOCALE_SET.put(Locale.US, MapLocale.US); + LOCALE_SET.put(Locale.CANADA_FRENCH, MapLocale.CANADA_FRENCH); + LOCALE_SET.put(Locale.CANADA, MapLocale.CANADA); + LOCALE_SET.put(Locale.CHINA, MapLocale.CHINA); + LOCALE_SET.put(Locale.PRC, MapLocale.PRC); + LOCALE_SET.put(Locale.ITALY, MapLocale.ITALY); + LOCALE_SET.put(Locale.UK, MapLocale.UK); + LOCALE_SET.put(Locale.JAPAN, MapLocale.JAPAN); + LOCALE_SET.put(Locale.KOREA, MapLocale.KOREA); + LOCALE_SET.put(Locale.GERMANY, MapLocale.GERMANY); + LOCALE_SET.put(Locale.FRANCE, MapLocale.FRANCE); + } + + private final LatLngBounds countryBounds; + private final String mapLanguage; + + /** + * Construct a new MapLocale instance using one of the map languages found in {@link Languages}. + * + * @param mapLanguage a non-null string which is allowed from {@link Languages} + * @since 0.1.0 + */ + public MapLocale(@NonNull @Languages String mapLanguage) { + this(mapLanguage, null); + } + + /** + * Construct a new MapLocale instance by passing in a LatLngBounds object. + * + * @param countryBounds non-null {@link LatLngBounds} object which wraps around the country + * @since 0.1.0 + */ + public MapLocale(@NonNull LatLngBounds countryBounds) { + this(LOCAL_NAME, countryBounds); + } + + /** + * /** + * Construct a new MapLocale instance using one of the map languages found in {@link Languages} + * and also passing in a LatLngBounds object. + * + * @param mapLanguage a non-null string which is allowed from {@link Languages} + * @param countryBounds {@link LatLngBounds} object which wraps around the country + * @since 0.1.0 + */ + public MapLocale(@NonNull @Languages String mapLanguage, @Nullable LatLngBounds countryBounds) { + this.countryBounds = countryBounds; + this.mapLanguage = mapLanguage; + } + + /** + * Returns the Map Language which can be fed directly into {@code textField} in runtime styling to + * change language. + * + * @return a string representing the map language code. + * @since 0.1.0 + */ + @NonNull + public String getMapLanguage() { + return mapLanguage; + } + + /** + * Returns a {@link LatLngBounds} which represents the viewport bounds which allow for the entire + * viewing of a country within the devices viewport. + * + * @return a {@link LatLngBounds} which can be used when user locations unknown but locale is + * @since 0.1.0 + */ + @Nullable + public LatLngBounds getCountryBounds() { + return countryBounds; + } + + /** + * When creating a new MapLocale, you'll need to associate a {@link Locale} so that + * {@link Locale#getDefault()} will find the correct corresponding {@link MapLocale}. + * + * @param locale a valid {@link Locale} instance shares a 1 to 1 relationship with the + * {@link MapLocale} + * @param mapLocale the {@link MapLocale} which shares a 1 to 1 relationship with the + * {@link Locale} + * @since 0.1.0 + */ + public static void addMapLocale(@NonNull Locale locale, @NonNull MapLocale mapLocale) { + LOCALE_SET.put(locale, mapLocale); + } + + /** + * Passing in a Locale, you are able to receive the {@link MapLocale} object which it is currently + * paired with. If this returns null, there was no matching {@link MapLocale} to go along with the + * passed in Locale. If you expected a non-null result, you should make sure you used + * {@link #addMapLocale(Locale, MapLocale)} before making this call. + * + * @param locale the locale which you'd like to recieve it's matching {@link MapLocale} if one exist + * @return the matching {@link MapLocale} if one exist, otherwise null + * @since 0.1.0 + */ + @Nullable + public static MapLocale getMapLocale(@NonNull Locale locale) { + return LOCALE_SET.get(locale); + } +} \ No newline at end of file diff --git a/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/package-info.java b/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/package-info.java new file mode 100644 index 000000000..6b045cd44 --- /dev/null +++ b/plugin-localization/src/main/java/com.mapbox.mapboxsdk.plugins.localization/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains the classes relevant to the Mapbox Localization Plugin. + * + * @since 0.1.0 + */ +package com.mapbox.mapboxsdk.plugins.localization; \ No newline at end of file diff --git a/plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/LocalizationPluginTest.java b/plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/LocalizationPluginTest.java new file mode 100644 index 000000000..c7f5c1dcd --- /dev/null +++ b/plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/LocalizationPluginTest.java @@ -0,0 +1,43 @@ +package com.mapbox.mapboxsdk.plugins.localization; + +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Locale; + +import static junit.framework.Assert.assertNotNull; +import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.Mockito.mock; + + +public class LocalizationPluginTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void sanity() throws Exception { + LocalizationPlugin localizationPlugin + = new LocalizationPlugin(mock(MapView.class), mock(MapboxMap.class)); + assertNotNull(localizationPlugin); + } + + @Test + @Ignore + public void setMapLanguage_localePassedInNotValid() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage(containsString("has no matching MapLocale object. You need to create")); + LocalizationPlugin localizationPlugin + = new LocalizationPlugin(mock(MapView.class), mock(MapboxMap.class)); + localizationPlugin.setMapLanguage(new Locale("foo", "bar")); + } +} \ No newline at end of file diff --git a/plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/MapLocaleTest.java b/plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/MapLocaleTest.java new file mode 100644 index 000000000..93c7d7948 --- /dev/null +++ b/plugin-localization/src/test/java/com/mapbox/mapboxsdk/plugins/localization/MapLocaleTest.java @@ -0,0 +1,28 @@ +package com.mapbox.mapboxsdk.plugins.localization; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Locale; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +public class MapLocaleTest { + + @Test + public void sanity() throws Exception { + MapLocale locale = new MapLocale(MapLocale.FRENCH, MapLocale.FRANCE_BBOX); + assertThat(locale.getMapLanguage(), equalTo(MapLocale.FRENCH)); + assertThat(locale.getCountryBounds(), equalTo(MapLocale.FRANCE_BBOX)); + } + + @Test + public void addMapLocale_doesGetAddedAndReferencedCorrectly() throws Exception { + Locale locale = new Locale("foo", "bar"); + MapLocale mapLocale = new MapLocale("abc"); + MapLocale.addMapLocale(locale, mapLocale); + MapLocale mapLocale1 = MapLocale.getMapLocale(locale); + Assert.assertThat(mapLocale1.getMapLanguage(), equalTo("abc")); + } +} diff --git a/settings.gradle b/settings.gradle index 7ac3b66d3..35c6282e6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,4 +5,5 @@ include ':plugin-building' include ':plugin-cluster' include ':plugin-geojson' include ':plugin-places' -include 'plugin-offline' +include ':plugin-offline' +include ':plugin-localization'