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

Language switching fixed #134

Merged
merged 7 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# MapTiler SDK Changelog

## 2.4.2
### Bug Fixes
- The language switching is now more robust and preserves the original formatting from the style (`Map.setPrimareyLangage()`) (https://github.com/maptiler/maptiler-sdk-js/pull/134)
jonathanlurie marked this conversation as resolved.
Show resolved Hide resolved

## 2.4.1
### Bug Fixes
- The class `AJAXError` is now imported as part of the `maplibregl` namespace (CommonJS limitation from Maplibre GL JS) (https://github.com/maptiler/maptiler-sdk-js/pull/129)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@maptiler/sdk",
"version": "2.4.1",
"version": "2.4.2",
"description": "The Javascript & TypeScript map SDK tailored for MapTiler Cloud",
"module": "dist/maptiler-sdk.mjs",
"types": "dist/maptiler-sdk.d.ts",
Expand Down
60 changes: 47 additions & 13 deletions src/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import type { ReferenceMapStyle, MapStyleVariant } from "@maptiler/client";
import { config, MAPTILER_SESSION_ID, type SdkConfig } from "./config";
import { defaults } from "./defaults";
import { MaptilerLogoControl } from "./MaptilerLogoControl";
import { combineTransformRequest, displayNoWebGlWarning, displayWebGLContextLostWarning } from "./tools";
import {
changeFirstLanguage,
combineTransformRequest,
displayNoWebGlWarning,
displayWebGLContextLostWarning,
} from "./tools";
import { getBrowserLanguage, Language, type LanguageInfo } from "./language";
import { styleToStyle } from "./mapstyle";
import { MaptilerTerrainControl } from "./MaptilerTerrainControl";
Expand Down Expand Up @@ -190,6 +195,7 @@ export class Map extends maplibregl.Map {
private terrainAnimationDuration = 1000;
private monitoredStyleUrls!: Set<string>;
private styleInProcess = false;
private originalLabelStyle = new window.Map<string, ExpressionSpecification | string>();

constructor(options: MapOptions) {
displayNoWebGlWarning(options.container);
Expand Down Expand Up @@ -730,6 +736,7 @@ export class Map extends maplibregl.Map {
style: null | ReferenceMapStyle | MapStyleVariant | StyleSpecification | string,
options?: StyleSwapOptions & StyleOptions,
): this {
this.originalLabelStyle.clear();
this.minimap?.setStyle(style);
this.forceLanguageUpdate = true;

Expand Down Expand Up @@ -1003,7 +1010,7 @@ export class Map extends maplibregl.Map {
let langStr = Language.LOCAL.flag;

// will be overwritten below
let replacer: ExpressionSpecification | string = `{${langStr}}`;
let replacer: ExpressionSpecification | string = ["get", langStr];

if (languageNonStyle.flag === Language.VISITOR.flag) {
langStr = getBrowserLanguage().flag;
Expand Down Expand Up @@ -1049,23 +1056,26 @@ export class Map extends maplibregl.Map {
];
} else if (languageNonStyle.flag === Language.AUTO.flag) {
langStr = getBrowserLanguage().flag;
replacer = ["case", ["has", langStr], ["get", langStr], ["get", Language.LOCAL.flag]];
replacer = ["coalesce", ["get", langStr], ["get", Language.LOCAL.flag]];
}

// This is for using the regular names as {name}
else if (languageNonStyle === Language.LOCAL) {
langStr = Language.LOCAL.flag;
replacer = `{${langStr}}`;
replacer = ["get", langStr];
}

// This section is for the regular language ISO codes
else {
langStr = languageNonStyle.flag;
replacer = ["case", ["has", langStr], ["get", langStr], ["get", Language.LOCAL.flag]];
replacer = ["coalesce", ["get", langStr], ["get", Language.LOCAL.flag]];
}

const { layers } = this.getStyle();

// True if it's the first time the language is updated for the current style
const firstPassOnStyle = this.originalLabelStyle.size === 0;

for (const genericLayer of layers) {
// Only symbole layer can have a layout with text-field
if (genericLayer.type !== "symbol") {
Expand Down Expand Up @@ -1102,17 +1112,41 @@ export class Map extends maplibregl.Map {
continue;
}

const textFieldLayoutProp = this.getLayoutProperty(id, "text-field");
let textFieldLayoutProp: string | maplibregl.ExpressionSpecification;

// If the label is not about a name, then we don't translate it
if (
typeof textFieldLayoutProp === "string" &&
(textFieldLayoutProp.toLowerCase().includes("ref") || textFieldLayoutProp.toLowerCase().includes("housenumber"))
) {
continue;
// Keeping a copy of the text-field sub-object as it is in the original style
if (firstPassOnStyle) {
textFieldLayoutProp = this.getLayoutProperty(id, "text-field");
this.originalLabelStyle.set(id, textFieldLayoutProp);
} else {
textFieldLayoutProp = this.originalLabelStyle.get(id) as string | maplibregl.ExpressionSpecification;
}

this.setLayoutProperty(id, "text-field", replacer);
// From this point, the value of textFieldLayoutProp is as in the original version of the style
// and never a mofified version

// Testing the different case where the text-field property should NOT be updated:
if (typeof textFieldLayoutProp === "string") {
// If the field is a string that DOES NOT contain an opening curly bracket.
// (This happens when the label is a hardcoded string that does not refer to a property)
if (!textFieldLayoutProp.includes("{")) {
continue;
}

// If the text field does not contain the "name" substring.
// This happens when dealing with {ref}, {housenumber}, {height}, etc.
if (!textFieldLayoutProp.includes("name")) {
continue;
}
jonathanlurie marked this conversation as resolved.
Show resolved Hide resolved

this.setLayoutProperty(id, "text-field", replacer);
}

// The value of text-field is an object
else {
const newReplacer = changeFirstLanguage(textFieldLayoutProp, replacer);
this.setLayoutProperty(id, "text-field", newReplacer);
}
}
}

Expand Down
42 changes: 42 additions & 0 deletions src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,45 @@ export function displayWebGLContextLostWarning(container: HTMLElement | string)
actualContainer.appendChild(errorMessageDiv);
// throw new Error(webglError);
}

/**
* Return true if the provided piece of style expression has the form ["get", "name:XX"]
* with XX benig a 2-letter is code for languages
*/
function isGetNameLanguage(subExpr: unknown): boolean {
if (!Array.isArray(subExpr)) return false;
if (subExpr.length !== 2) return false;
if (subExpr[0] !== "get") return false;
if (typeof subExpr[1] !== "string") return false;
if (!subExpr[1].startsWith("name:")) return false;

return true;
}

/**
* In a text-field style property (as an object, not a string) the langages that are specified as
* ["get", "name:XX"] are replaced by the proved replacer (also an object).
* This replacement happened regardless of how deep in the object the flag is.
* Note that it does not replace the occurences of ["get", "name"] (local names)
jonathanlurie marked this conversation as resolved.
Show resolved Hide resolved
*/
export function changeFirstLanguage(
origExpr: maplibregl.ExpressionSpecification,
replacer: maplibregl.ExpressionSpecification | string,
): maplibregl.ExpressionSpecification {
const expr = structuredClone(origExpr) as maplibregl.ExpressionSpecification;

const exploreNode = (subExpr: maplibregl.ExpressionSpecification | string) => {
if (typeof subExpr === "string") return;

for (let i = 0; i < subExpr.length; i += 1) {
if (isGetNameLanguage(subExpr[i])) {
subExpr[i] = structuredClone(replacer);
} else {
exploreNode(subExpr[i] as maplibregl.ExpressionSpecification | string);
}
}
};

exploreNode(expr);
return expr;
}
Loading