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

Feature/localization #480

Merged
merged 15 commits into from
Jun 22, 2021
Merged

Feature/localization #480

merged 15 commits into from
Jun 22, 2021

Conversation

neelmistry94
Copy link
Contributor

@neelmistry94 neelmistry94 commented Jun 17, 2021

PRs must be submitted under the terms of our Contributor License Agreement CLA.

Fixes: Localization Feature

Pull request checklist:

  • Briefly describe the changes in this PR.
  • Include before/after visuals or gifs if this PR includes visual changes.
  • Write tests for all new functionality. If tests were not written, please explain why.
  • Add example if relevant.
  • Document any changes to public APIs.
  • Apply changelog label ('breaking change', 'bug 🪲', 'build', 'docs', 'feature 🍏', 'performance ⚡', 'testing 💯') or use the label 'skip changelog'
  • Add an entry inside this element for inclusion in the 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 supportedLocale</changelog>.
  • Update the migration guide, API Docs, Markdown files - Readme, Developing, etc

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 the mapView. It will then be converted back to an expression type and the layer will be updated.

User impact (optional)

@neelmistry94 neelmistry94 added the feature 🍏 When working on a new feature or feature enhancement label Jun 17, 2021
Comment on lines 39 to 43
public var locale: Locale = Locale.current {
didSet {
mapboxMap.style.localizeLabels(into: locale)
}
}
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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

@neelmistry94 neelmistry94 marked this pull request as ready for review June 22, 2021 14:50

/// 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) {
Copy link
Contributor

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) { .. } 

Copy link
Contributor Author

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

@julianrex
Copy link
Contributor

Noting that this PR does not support Locale.preferredLanguages (which < 10.0.0 did). This is something that we should address in a later PR @tobrun @sbma44

@neelmistry94 neelmistry94 merged commit 13f10e6 into main Jun 22, 2021
Copy link
Contributor

@1ec5 1ec5 left a 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.

Comment on lines +25 to +37
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)
}
Copy link
Contributor

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.

Comment on lines +11 to +13
let symbolLayers = allLayerIdentifiers.filter { layer in
return layer.type == .symbol
}
Copy link
Contributor

@1ec5 1ec5 Jun 22, 2021

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 {
Copy link
Contributor

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 Locales to avoid unchecked usage of raw values.

Comment on lines +19 to +22
let expressionCoalesce = try NSRegularExpression(pattern: "\\[\"get\",\\s*\"(name_.{2,7})\"\\]",
options: .caseInsensitive)
let expressionAbbr = try NSRegularExpression(pattern: "\\[\"get\",\\s*\"abbr\"\\]",
options: .caseInsensitive)
Copy link
Contributor

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 {
Copy link
Contributor

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 {
Copy link
Contributor

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.

@macdrevx macdrevx deleted the feature/localization branch October 21, 2021 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature 🍏 When working on a new feature or feature enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants