@panoramax/web-viewer 3.0.2-develop-a8ea8e60-develop-f1bb641f → 3.1.0-develop-537ffe27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,3 +83,17 @@ A supplementary layer _grid_ can be made available for low-zoom overview:
83
83
 
84
84
  - Available on zoom levels < 6
85
85
  - Available properties: `id` (grid cell ID), `nb_pictures` (amount of pictures), `coef` (value from 0 to 1, relative quantity of available pictures)
86
+
87
+ ### Labels translation
88
+
89
+ If your vector tiles support multiple languages, you can set in your `style.json` the list of supported languages :
90
+
91
+ ```json
92
+ {
93
+ "metadata": {
94
+ "panoramax:locales": ["fr", "en", "latin"]
95
+ }
96
+ }
97
+ ```
98
+
99
+ The viewer will try to find the best matching `name:LANG` property according to user browser settings.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "3.0.2-develop-a8ea8e60-develop-f1bb641f",
3
+ "version": "3.1.0-develop-537ffe27",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "build/index.js",
6
6
  "author": "Panoramax team",
package/src/utils/I18n.js CHANGED
@@ -10,7 +10,13 @@ const TRANSLATIONS = {
10
10
  "de": T_de, "en": T_en, "es": T_es, "fr": T_fr, "hu": T_hu, "zh_Hant": T_zh_Hant,
11
11
  };
12
12
 
13
- const autoDetectLocale = () => {
13
+ /**
14
+ * Find best matching language regarding of list of supported languages and browser accepted languages
15
+ * @param {str[]} supportedTranslations List of supported languages
16
+ * @param {str} fallback The fallback language
17
+ * @returns The best matching language
18
+ */
19
+ export function autoDetectLocale(supportedTranslations, fallback) { // eslint-ignore import/no-unused-modules
14
20
  for (const navigatorLang of window.navigator.languages) {
15
21
  let language = navigatorLang;
16
22
  // Convert browser code to weblate code
@@ -30,13 +36,14 @@ const autoDetectLocale = () => {
30
36
  }
31
37
  break;
32
38
  }
33
- const pair = Object.entries(TRANSLATIONS).find((pair) => pair[0] === language);
39
+ const pair = supportedTranslations.find((pair) => pair === language);
34
40
  if (pair) {
35
- return pair[0];
41
+ return pair;
36
42
  }
37
43
  }
38
- return FALLBACK_LOCALE;
39
- };
44
+ return fallback;
45
+ }
46
+
40
47
  /**
41
48
  * Get text labels translations in given language
42
49
  *
@@ -50,7 +57,7 @@ export function getTranslations(lang = "") {
50
57
 
51
58
  // No specific lang set -> use browser lang
52
59
  if(!lang) {
53
- lang = autoDetectLocale();
60
+ lang = autoDetectLocale(Object.keys(TRANSLATIONS), FALLBACK_LOCALE);
54
61
  }
55
62
 
56
63
  // Lang exists -> send it
package/src/utils/Map.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import maplibregl from "!maplibre-gl";
3
3
  import LoaderImg from "../img/marker.svg";
4
4
  import { COLORS } from "./Utils";
5
+ import { autoDetectLocale } from "./I18n";
5
6
 
6
7
  export const DEFAULT_TILES = "https://panoramax.openstreetmap.fr/pmtiles/basic.json";
7
8
  export const RASTER_LAYER_ID = "gvs-aerial";
@@ -152,7 +153,47 @@ export function combineStyles(parent, options) {
152
153
  }
153
154
  }
154
155
  });
155
-
156
+
157
+ // TODO : remove override once available in default Panoramax style
158
+ if(!style.metadata["panoramax:locales"]) {
159
+ style.metadata["panoramax:locales"] = ["fr", "en", "de", "es", "ru", "pt", "zh", "hi", "latin"];
160
+ }
161
+
162
+ // Override labels to use appropriate language
163
+ if(style.metadata["panoramax:locales"]) {
164
+ const prefLang = autoDetectLocale(style.metadata["panoramax:locales"], "latin");
165
+ style.layers.forEach(l => {
166
+ if(isLabelLayer(l) && l.layout["text-field"].includes("name:latin")) {
167
+ l.layout["text-field"] = [
168
+ "coalesce",
169
+ ["get", `name:${prefLang}`],
170
+ ["get", "name:latin"],
171
+ ["get", "name"]
172
+ ];
173
+ }
174
+ });
175
+ }
176
+
177
+ // Fix for capital cities
178
+ const citiesLayer = style.layers.find(l => l.id == "place_label_city");
179
+ let capitalLayer = style.layers.find(l => l.id == "place_label_capital");
180
+ if(citiesLayer && !capitalLayer) {
181
+ // Create capital layer from original city style
182
+ capitalLayer = JSON.parse(JSON.stringify(citiesLayer));
183
+ capitalLayer.id = "place_label_capital";
184
+ capitalLayer.filter.push(["<=", "capital", 2]);
185
+
186
+ // Edit original city to make it less import
187
+ citiesLayer.filter.push([">", "capital", 2]);
188
+ citiesLayer.paint = {
189
+ "text-color": "hsl(0,0%,15%)",
190
+ "text-halo-blur": 0.5,
191
+ "text-halo-color": "hsl(0,0%,100%)",
192
+ "text-halo-width": 0.8,
193
+ };
194
+ style.layers.push(capitalLayer);
195
+ }
196
+
156
197
  return style;
157
198
  }
158
199
 
@@ -26,6 +26,7 @@ jest.mock("maplibre-gl", () => ({
26
26
  return {
27
27
  layers: [],
28
28
  sources: {},
29
+ metadata: {},
29
30
  };
30
31
  }
31
32
  resize() {;}
@@ -52,7 +53,7 @@ const createParent = () => ({
52
53
  getDataBbox: jest.fn(),
53
54
  getPicturesTilesUrl: jest.fn(),
54
55
  _getMapRequestTransform: jest.fn(),
55
- getMapStyle: () => ({ sources: {}, layers: [] }),
56
+ getMapStyle: () => ({ sources: {}, layers: [], metadata: {} }),
56
57
  },
57
58
  _t: {
58
59
  maplibre: {},
@@ -1,4 +1,87 @@
1
- import { getTranslations } from "../../src/utils/I18n";
1
+ import { autoDetectLocale, getTranslations } from "../../src/utils/I18n";
2
+
3
+ describe("autoDetectLocale", () => {
4
+ // Mock the window.navigator.languages
5
+ const originalNavigatorLanguages = window.navigator.languages;
6
+
7
+ afterEach(() => {
8
+ // Reset window.navigator.languages after each test
9
+ Object.defineProperty(window.navigator, "languages", {
10
+ value: originalNavigatorLanguages,
11
+ configurable: true
12
+ });
13
+ });
14
+
15
+ it("returns matched language from supportedTranslations", () => {
16
+ Object.defineProperty(window.navigator, "languages", {
17
+ value: ["fr-FR", "en-US"],
18
+ configurable: true
19
+ });
20
+ const supportedTranslations = ["en", "fr", "es"];
21
+ const fallback = "en";
22
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("fr");
23
+ });
24
+
25
+ it("returns fallback when no match is found", () => {
26
+ Object.defineProperty(window.navigator, "languages", {
27
+ value: ["de-DE", "it-IT"],
28
+ configurable: true
29
+ });
30
+ const supportedTranslations = ["en", "fr", "es"];
31
+ const fallback = "en";
32
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("en");
33
+ });
34
+
35
+ it("returns zh_Hant for Chinese Traditional locales", () => {
36
+ Object.defineProperty(window.navigator, "languages", {
37
+ value: ["zh-TW"],
38
+ configurable: true
39
+ });
40
+ const supportedTranslations = ["zh_Hant", "zh_Hans", "en"];
41
+ const fallback = "en";
42
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("zh_Hant");
43
+ });
44
+
45
+ it("returns zh_Hans for Chinese Simplified locales", () => {
46
+ Object.defineProperty(window.navigator, "languages", {
47
+ value: ["zh-CN"],
48
+ configurable: true
49
+ });
50
+ const supportedTranslations = ["zh_Hant", "zh_Hans", "en"];
51
+ const fallback = "en";
52
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("zh_Hans");
53
+ });
54
+
55
+ it("returns first matched language even when navigator language has region", () => {
56
+ Object.defineProperty(window.navigator, "languages", {
57
+ value: ["fr-CA", "en-US"],
58
+ configurable: true
59
+ });
60
+ const supportedTranslations = ["fr", "en"];
61
+ const fallback = "en";
62
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("fr");
63
+ });
64
+
65
+ it("returns fallback when supportedTranslations is empty", () => {
66
+ Object.defineProperty(window.navigator, "languages", {
67
+ value: ["fr-FR"],
68
+ configurable: true
69
+ });
70
+ const supportedTranslations = [];
71
+ const fallback = "en";
72
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("en");
73
+ });
74
+
75
+ it("handles language codes with more than two characters", () => {
76
+ Object.defineProperty(window.navigator, "languages", {
77
+ value: ["pt-BR", "en-US"],
78
+ configurable: true
79
+ });
80
+ const supportedTranslations = ["pt", "en"];
81
+ const fallback = "en";
82
+ expect(autoDetectLocale(supportedTranslations, fallback)).toBe("pt");
83
+ });
84
+ });
2
85
 
3
86
  describe("getTranslations", () => {
4
87
  it("works with default lang", () => {