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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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.setPrimaryLangage()`) (https://github.com/maptiler/maptiler-sdk-js/pull/134)

## 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
78 changes: 65 additions & 13 deletions src/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ 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,
checkNamePattern,
combineTransformRequest,
computeLabelsLocalizationMetrics,
displayNoWebGlWarning,
displayWebGLContextLostWarning,
replaceLanguage,
} from "./tools";
import { getBrowserLanguage, Language, type LanguageInfo } from "./language";
import { styleToStyle } from "./mapstyle";
import { MaptilerTerrainControl } from "./MaptilerTerrainControl";
Expand Down Expand Up @@ -190,6 +198,8 @@ export class Map extends maplibregl.Map {
private terrainAnimationDuration = 1000;
private monitoredStyleUrls!: Set<string>;
private styleInProcess = false;
private originalLabelStyle = new window.Map<string, ExpressionSpecification | string>();
private isStyleLocalized = false;

constructor(options: MapOptions) {
displayNoWebGlWarning(options.container);
Expand Down Expand Up @@ -730,6 +740,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 +1014,7 @@ export class Map extends maplibregl.Map {
let langStr = Language.LOCAL.flag;

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

if (languageNonStyle.flag === Language.VISITOR.flag) {
langStr = getBrowserLanguage().flag;
Expand Down Expand Up @@ -1049,23 +1060,32 @@ 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;

// Analisis on all the label layers to check the languages being used
if (firstPassOnStyle) {
const labelsLocalizationMetrics = computeLabelsLocalizationMetrics(layers, this);
this.isStyleLocalized = Object.keys(labelsLocalizationMetrics.localized).length > 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 +1122,49 @@ 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;
}

// 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") {
// When the original style is localized (this.isStyleLocalized is true), we do not modify the {name} because they are
// very likely to be only fallbacks.
// When the original style is not localized (this.isStyleLocalized is false), the occurences of "{name}"
// should be replaced by localized versions with fallback to local language.

const { contains, exactMatch } = checkNamePattern(textFieldLayoutProp, this.isStyleLocalized);

// If the current text-fiels does not contain any "{name:xx}" pattern
if (!contains) continue;

// In case of an exact match, we replace by an object representation of the label
if (exactMatch) {
this.setLayoutProperty(id, "text-field", replacer);
} else {
// In case of a non-exact match (such as "foo {name:xx} bar" or "foo {name} bar", depending on localization)
// we create a "concat" object expresion composed of the original elements with new replacer
// in-betweem
const newReplacer = replaceLanguage(textFieldLayoutProp, replacer, this.isStyleLocalized);

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

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

Expand Down
Loading
Loading