Localization plugin#74
Conversation
|
And yes, I will create a test class too |
|
@langsmith if you aren't adding any instrumentation tests, feel free to remove that example file. |
| String deviceLanguage = Locale.getDefault().getLanguage(); | ||
| for (Layer layer : listOfMapLayers) { | ||
| // TODO: Fix if() statement? What should if statement be looking for in layer ids? | ||
| if (layer.getId().contains("")) { |
There was a problem hiding this comment.
Briefly looking over this, I think you are looking to change all Symbol layers? For that you can do if(layer instanceof SymbolLayer) {}
| @@ -0,0 +1,25 @@ | |||
| # Add project specific ProGuard rules here. | |||
There was a problem hiding this comment.
Doesn't look like this ProGuard files needed.
| @@ -0,0 +1,3 @@ | |||
| <resources> | |||
| <string name="app_name">localization</string> | |||
|
Note: Cameron's review requests have been addressed. |
There does need to be a list hard-coded somewhere, until mapbox/tilejson-spec#14 is implemented. (Someday…) Note that the canonical list of available languages is in the Mapbox Streets source v7 documentation. |
| public LocalizationPlugin(@NonNull final MapboxMap mapboxMap) { | ||
| listOfMapLayers = mapboxMap.getLayers(); | ||
| String deviceLanguage = Locale.getDefault().getLanguage(); | ||
| for (Layer layer : listOfMapLayers) { |
There was a problem hiding this comment.
What if the style includes layers that use a source other than Mapbox Streets? For example, there may be a layer based on a custom Studio dataset that also has a {name} property but no corresponding {name_*} properties.
There was a problem hiding this comment.
Now checking for Mapbox-only sources with this commit
There was a problem hiding this comment.
It turns out that commit doesn’t actually filter by source. Instead, it reruns the code once for each source, meaning that each layer gets processed one time per source. The Android map SDK apparently lacks a way to determine the source that a layer is using: mapbox/mapbox-gl-native#12691.
| String deviceLanguage = Locale.getDefault().getLanguage(); | ||
| for (Layer layer : listOfMapLayers) { | ||
| if (layer instanceof SymbolLayer) { | ||
| layer.setProperties(PropertyFactory.textField(String.format("{name_%s}", deviceLanguage))); |
There was a problem hiding this comment.
What if the symbol layer had no textField to begin with, or what if it was set to {ref}? Would highway shields start displaying road names instead of route numbers?
Note that, as a cartographic choice, most Mapbox-designed styles use {abbr} at low zoom levels. However, {abbr} is only available in English, so it should also be replaced by {name_*} when the language isn’t English. (See mapbox/mapbox-gl-native#9902 for the corresponding iOS/macOS issue.)
There was a problem hiding this comment.
It can definitely be cleaned up, but here's mytextField check for now 😕 :
{abbr} check and adjustment:
| /** | ||
| * Sets map text to Traditional Chinese. | ||
| */ | ||
| public void setMapTextToTraditionalChinese() { |
There was a problem hiding this comment.
{name_zh} is Chinese in general, not necessarily Traditional Chinese. For example, country labels are in Simplified Chinese, as are places in 🇨🇳.
There was a problem hiding this comment.
Cool. Changed the method name to setMapTextToChinese() and added explanatory javadocs for both methods. Now reads as 👇
/**
* Sets map text to Chinese.
*
* This method uses simplified Chinese characters for custom label layers: #country_label, #state_label,
* and #marine_label. All other label layers are sourced from OpenStreetMap and may contain one of several dialects
* and either simplified or traditional Chinese characters in the {name_zh} field.
*/
public void setMapTextToChinese() {
setMapTextLanguage("zh");
}
/**
* Sets map text to Simplified Chinese.
*
* Using this method is similar to setMapTextToChinese() (see above), except any Traditional Chinese
* characters are automatically transformed to Simplified Chinese.
*/
public void setMapTextToSimplifiedChinese() {
setMapTextLanguage("zh-Hans");
}
| */ | ||
| public LocalizationPlugin(@NonNull final MapboxMap mapboxMap) { | ||
| listOfMapLayers = mapboxMap.getLayers(); | ||
| String deviceLanguage = Locale.getDefault().getLanguage(); |
There was a problem hiding this comment.
According to this documentation, getLanguage() returns deprecated ISO 639 codes, such as iw for Hebrew instead of the current standard he. For forward-compatibility, consider using one of the other available methods.
Additionally, will this method return zh-Hans when the user chooses Chinese (Simplified) in their device settings?
There was a problem hiding this comment.
There was a problem hiding this comment.
There was a problem hiding this comment.
There was a problem hiding this comment.
There was a problem hiding this comment.
I made this commit which checks for and adjusts known deprecated ISO 639 codes. After lots of research, it doesn't seem like there's a single method that covers the backward and forwards compatibility that @1ec5 mentions above. Let's let my commit ✅ this comment for now so that this isn't a blocker for getting this plugin released.
There was a problem hiding this comment.
This commit addresses simplified/traditional Chinese selection
There was a problem hiding this comment.
yea, looked into it. For some reason, I think I read that it wouldn't work both ways, but I don't remember exactly why now 😵 . That link you just gave above, does sound like it would work for covering back and forwards compatibility. Will look back into it
There was a problem hiding this comment.
Using toLanguageTag() if api level is above 21.
Both issues brought up in OP have been addressed.
|
Thanks @1ec5 . Lots more to learn/fix 👍
Are there specific changes that I need to make related to ☝️ or was that just a comment? Couldn't tell |
No changes needed for now. The canonical list is maintained by the cartography team and is more likely to be kept up-to-date than the /help document. |
| /** | ||
| * Sets map text to Simplified Chinese. | ||
| * | ||
| * Using his method is similar to setMapTextToChinese() (see above), except any Traditional Chinese |
| /** | ||
| * Sets map text to Simplified Chinese. | ||
| * | ||
| * Using his method is similar to setMapTextToChinese() (see above), except any Traditional Chinese |
There was a problem hiding this comment.
(see above)
Doesn’t JavaDoc autolink method references? That would make glosses like this unnecessary.
|
|
||
| <item | ||
| android:id="@+id/chinese" | ||
| android:title="Traditional Chinese" |
1ec5
left a comment
There was a problem hiding this comment.
Looks good to me (a total nondroid).
|
I cleaned up a bunch of the code and moved things around to reflect the latest changes made in master. Would be good to continue working on this and merging it. |
|
Thanks @cammace |
| */ | ||
| private boolean sourceIsFromMapbox(Source singleSource) { | ||
| return singleSource instanceof VectorSource | ||
| && ((VectorSource) singleSource).getUrl().contains("mapbox://mapbox.mapbox"); |
There was a problem hiding this comment.
Many Mapbox-designed styles – indeed, many styles designed in Studio – use a single composite source whose URL is mapbox://mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v7. By only checking if the URL begins with mapbox://mapbox.mapbox, this code may inadvertently snag sources that consist of mapbox.satellite or mapbox.terrain composited with a Studio-managed dataset.
The iOS/macOS map SDK’s implementation considers the source to be the Mapbox Streets source – the only one this plugin can reliably localize – if both of the following conditions hold:
- The URL scheme is
mapbox; that is, the URL begins withmapbox://. - The URL’s hostname is a comma-delimited list containing either
mapbox.mapbox-streets-v7ormapbox.mapbox-streets-v6. (The latter doesn’t matter so much at this point.)
There was a problem hiding this comment.
Thanks @1ec5 . Will adjust based on your comments. 🚀
There was a problem hiding this comment.
Although I don't create String arrays like the iOS/macOS map SDK’s implementation, I believe that the way I've handled scheme/hostname check will work just fine:
There was a problem hiding this comment.
Yeah, I think it’s robust enough. It’s unlikely that someone would create an account named likemapbox and upload a dataset named mapbox-streets-v71 to it. It’s also going to be awhile before the cartography team gets a chance to release Mapbox Streets source v60.
| layer.setProperties(textField(String.format("{name_%s}", languageToSetMapTo))); | ||
| } | ||
| if (((SymbolLayer) layer).getTextField().getValue().contains("abbr") | ||
| && !getDeviceLanguage().equals("en")) { |
There was a problem hiding this comment.
This assumes the style was set to use name_en in the first place, which is true about most Mapbox-designed styles like Mapbox Streets. However, it’d be totally reasonable for a style to use name (local language) right out of the gate.
For reference, the iOS and macOS map SDKs preserve the style’s original language (e.g., English) unless the localizesLabels feature is enabled. Once the feature is enabled, it changes every name and name_* to the device language, falling back to name_en if the device language is unsupported. So whether the style originally used name_en, name, or name_zh, the localization feature will still take effect.
(The Mapbox GL Language plugin for GL JS has a defaultLanguage option instead of falling back to English.)
There was a problem hiding this comment.
Removed the && !getDeviceLanguage().equals("en") assumption in this commit 86be093
The comment below addresses the checking and fallback functions that I've built in.
| Toast.makeText(this, R.string.map_not_localized, Toast.LENGTH_SHORT).show(); | ||
| mapIsLocalized = false; | ||
| } else { | ||
| localizationPlugin.setMapTextLanguage(localizationPlugin.getDeviceLanguage()); |
There was a problem hiding this comment.
If the device language happens to be a language that the Mapbox Streets source doesn’t support, such as Thai, this plugin will end up changing all the layers to {name_th}, producing undesired results. This plugin needs to check that the device language is supported, falling back to either English or the local language. (The iOS and macOS map SDKs fall back to English, whereas the Mapbox GL Language plugin for GL JS falls back to a default language that the developer specifies as an option.)
There was a problem hiding this comment.
I believe that af59d95 now fixes this. I've added a preferred backup language parameter for the initialization of the plugin.
I also added a check to make sure that a language is supported by Mapbox Streets style source
If the check detects that the device language isn't supported, then the backup language is checked and set. English is the final fall back if the backup language isn't supported by Mapbox Streets style source either
| * @param mapboxMap the MapboxMap to apply the localization plugin to | ||
| * @param mapboxMap the MapboxMap to apply the localization plugin to | ||
| * @param backupLanguage the default language for the map to fall back to if the device language is not | ||
| * supported by Open Street Maps. |
There was a problem hiding this comment.
Nit: OpenStreetMap is capable of providing names in every language that has an ISO 639 code. (Coverage is uneven from one language to another.) It’s the Mapbox Streets source that’s currently limited to a handful of languages.
| if (((SymbolLayer) layer).getTextField().getValue().contains("abbr") | ||
| && !getDeviceLanguage().equals("en")) { | ||
| if (((SymbolLayer) layer).getTextField().getValue().contains("name") | ||
| || ((SymbolLayer) layer).getTextField().getValue().equals("abbr")) { |
There was a problem hiding this comment.
If the language is English and the text field is {abbr}, we should use {abbr}, because that field is written in English.
|
Per @1ec5 's suggestion, let's add installation instructions to this help page before releasing this plugin. I will handle this. |
| if (((SymbolLayer) layer).getTextField().getValue().contains("{name") | ||
| || !getDeviceLanguage().equals("en") && ((SymbolLayer) layer).getTextField().getValue().contains("{abbr}") | ||
| ) { | ||
| layer.setProperties(textField(String.format("{name_%s}", languageToSetMapTo))); |
There was a problem hiding this comment.
If text-field was originally set to something like {name_en} ({name}), this would replace it with just {name_en} for English speakers. I think it would be better to get the text field value as a string, then find and replace {name} and {name_*} with {name_xy} (where xy is languageToSetMapTo), then set the modified string as the text field.
There was a problem hiding this comment.
I addressed this issue by using a regrex and replacing only the {name_*} part in textfields.
| private boolean layerHasAdjustableTextField(Layer singleLayer) { | ||
| return singleLayer instanceof SymbolLayer && (((SymbolLayer) singleLayer).getTextField() != null | ||
| && (((SymbolLayer) singleLayer).getTextField().getValue() != null | ||
| && !(((SymbolLayer) singleLayer).getTextField().getValue().isEmpty()))); |
There was a problem hiding this comment.
As tail work, consider making this plugin also affect layers whose text-field property is set to a function. In the Mapbox Streets style, for example, U.S. state labels are a function with stops for either {name_en} or {abbr} based on the zoom level.
Without accounting for functions, there will be some inconsistency in state and country labels at low zoom levels. However, expressions will be available in the next release, so it might not be worth spending much effort accommodating functions in the meantime.
/ref mapbox/mapbox-gl-native#10713 mapbox/mapbox-gl-native#10944
There was a problem hiding this comment.
However, expressions will be available in the next release, so it might be worth spending much effort accommodating functions in the meantime.
I'd prefer merging this PR and fixing this in a different branch in the future when we switch to 6.0.0.
There was a problem hiding this comment.
@cammace Is there a ticket in this repo where we're tracking this tail work?
9b8a22b to
a65da9c
Compare
9a639ae to
b228ad5
Compare



This plugin resolves #36 and enables a developer to localize the map text to the language set on the Android device.
The plugin also has individual methods for converting the map to each of the languages that we support. Anyways, here it is.
cc @tobrun @1ec5 @jcsg