-
Notifications
You must be signed in to change notification settings - Fork 149
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
Feature/localization #480
Feature/localization #480
Conversation
f3cd789
to
8be4ca5
Compare
public var locale: Locale = Locale.current { | ||
didSet { | ||
mapboxMap.style.localizeLabels(into: locale) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be an unsupported Locale?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can, but in that case, we will default to the local case if there is no data that exists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to make that behavior more explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could add a check when passed in to see if it is under one of our supported languages. If so, then continue, otherwise don't do anything
|
||
/// This function creates an expression that will localize the `textField` property of a `SymbolLayer` | ||
/// - Parameter locale: A `SupportedLanguage` based `Locale` | ||
internal func localizeLabels(into locale: Locale) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nav SDK has a requirement that not all labels are localized I think.. They want to exempt road labels for example from any localization.
To help them do this, it's probably best to break this function up into some functions like this:
public func localizeAllLabels(into locale: Locale) { .. }
public func localizeLabel(withId id: String, into locale: Locale) { .. }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@julianrex @tobrun This feels like something we need to align on. This functionality does not exist on Android
Noting that this PR does not support |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for implementing this important feature. Here’s some belated feedback based on lessons learned in the past.
let tempLayer = try layer(withId: layerInfo.id) as SymbolLayer | ||
|
||
if var stringExpression = String(data: try JSONEncoder().encode(tempLayer.textField), encoding: .utf8), | ||
stringExpression != "null" { | ||
stringExpression.updateExpression(replacement: replacement, regex: expressionCoalesce) | ||
stringExpression.updateExpression(replacement: replacement, regex: expressionAbbr) | ||
|
||
// Turn the new json string back into an Expression | ||
let data = stringExpression.data(using: .utf8) | ||
let convertedExpression = try JSONSerialization.jsonObject(with: data!, options: []) | ||
|
||
try setLayerProperty(for: tempLayer.id, property: "text-field", value: convertedExpression) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code should be extracted into a public method, so that a map optimized for turn-by-turn navigation can localize place and POI labels but not road labels.
let symbolLayers = allLayerIdentifiers.filter { layer in | ||
return layer.type == .symbol | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not every symbol layer should be forced to have a name
-based text-field
, which is specific to the Mapbox Streets source. A symbol layer may be backed by a vector tileset other than Mapbox Streets or a GeoJSON source; that source’s features may have name_en
properties but not name
properties. There’s code elsewhere that checks the source URL; it would be useful to conditionalize all localization on it, not just detection of Streets source v7.
if tempSource.url?.contains("mapbox.mapbox-streets-v7") == true { | ||
if locale.identifier.contains("zh") { | ||
// v7 styles does not support value of "name_zh-Hant" | ||
if locale.identifier == SupportedLanguage.traditionalChinese.rawValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace SupportedLanguage with an array of Locale
s to avoid unchecked usage of raw values.
let expressionCoalesce = try NSRegularExpression(pattern: "\\[\"get\",\\s*\"(name_.{2,7})\"\\]", | ||
options: .caseInsensitive) | ||
let expressionAbbr = try NSRegularExpression(pattern: "\\[\"get\",\\s*\"abbr\"\\]", | ||
options: .caseInsensitive) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s unnecessary to convert the entire expression to JSON, then to a string, then monkeypatch the string. That defeats the purpose of having a structured, type-safe representation of an expression.
Consider adapting the implementation in mapbox/mapbox-navigation-ios#2933 that recurses into the expression, surgically replacing occurrences of name
or name_*
with the coalescing expression. That approach is much more reliable and maintainable than trying to account for the variety of possible text-field
values with finding-and-replacing in source code.
@@ -34,6 +35,17 @@ open class MapView: UIView { | |||
/// Controls the addition/removal of annotations to the map. | |||
public internal(set) var annotations: AnnotationOrchestrator! | |||
|
|||
/// Property that describes the current language for `SymbolLayer.textField` | |||
public var locale: Locale = Locale.current { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Locale.current
only matches the language that the iOS UI is currently displayed in, which can differ from both the surrounding application and the user’s preference. Bundle.preferredLocalizations
has the advantage of matching the language that the rest of the application is currently displayed in. Locale.preferredLanguages
has the advantage of matching the user’s preferred content language settings, even if the application is unavailable in the preferred language.
do { | ||
let tempSource = try source(withId: sourceInfo.id) as VectorSource | ||
|
||
if tempSource.url?.contains("mapbox.mapbox-streets-v7") == true { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tileset ID always occurs in the hostname part of the URL, so compare against URL.host
for additional robustness.
PRs must be submitted under the terms of our Contributor License Agreement CLA.
Fixes: Localization Feature
Pull request checklist:
mapbox-maps-ios
changelog:<changelog>Adds localization support for v10 Maps SDK. This can be used by setting the
mapView.locale. There is a
SupportedLanguagesenum that will allow you to create a supported
Locale</changelog>
.Summary of changes
This is the introduction for Localization Support in Maps SDK v10. We will take the existing expression found on a
SymbolLayer.textField
and convert it to a JSON String. Replace all instances of the expression["get","name_xx"]
with the new locale that can be set on themapView
. It will then be converted back to an expression type and the layer will be updated.User impact (optional)