diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e801670..8d63d5d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,4 +3,8 @@ module.exports = { parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], root: true, + rules: { + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-explicit-any": "warn", + } }; \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b34401..dd8d9af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # MapTiler Client Changelog -## DEVEL +## 2.0.0 ### New Features - Added `matching_text` and `matching_place_name` properties to geocoding feature response - Added `road` to geocoding `types` - ### Bug Fixes ### Others +- Languages are now listed in the Client library ## 1.8.1 ### Bug Fixes diff --git a/examples/test-browser-es.html b/examples/test-browser-es.html index 0414652..073562f 100644 --- a/examples/test-browser-es.html +++ b/examples/test-browser-es.html @@ -21,16 +21,13 @@ coordinates, data, staticMaps, - LanguageGeocoding, } from '../dist/maptiler-client.mjs'; - console.log(LanguageGeocoding); - // The API key must be in the URL param as `?key=ABCD1234` config.apiKey = (new URLSearchParams(location.search)).get("key"); async function testGeocoding() { - console.log(await geocoding.forward('bordeaux', {language: [geocoding.language.SPANISH, geocoding.language.ENGLISH]})); + console.log(await geocoding.forward('bordeaux', {language: [geocoding.Language.SPANISH, geocoding.Language.ENGLISH]})); console.log(await geocoding.forward('bordeaux', { bbox: [-7.638157121837139, 33.595474163650685, -7.624914050102235, 33.600446760011856] })); diff --git a/examples/test-browser-umd.html b/examples/test-browser-umd.html index f12cd2a..b0890ab 100644 --- a/examples/test-browser-umd.html +++ b/examples/test-browser-umd.html @@ -19,9 +19,9 @@ maptilerClient.config.apiKey = (new URLSearchParams(location.search)).get("key"); async function testGeocoding() { - console.log(await maptilerClient.geocoding.forward('bordeaux', {language: [maptilerClient.LanguageGeocoding.SPANISH, maptilerClient.LanguageGeocoding.ENGLISH]})); - console.log(await maptilerClient.geocoding.forward('bordeaux')); - console.log(await maptilerClient.geocoding.reverse([6.249638, 46.402056], {language: ['es', 'en']})); + console.log(await maptilerClient.geocoding.forward('bordeaux', {language: [maptilerClient.Language.SPANISH, maptilerClient.Language.ENGLISH]})); + // console.log(await maptilerClient.geocoding.forward('bordeaux')); + // console.log(await maptilerClient.geocoding.reverse([6.249638, 46.402056], {language: ['es', 'en']})); } async function testGeolocation() { @@ -79,11 +79,11 @@ (async () => { - // await testGeocoding(); + await testGeocoding(); // await testGeolocation(); // await testCoordinates(); // await testData(); - staticMapTest(); + // staticMapTest(); })() diff --git a/examples/test-node.js b/examples/test-node.js index 55dd93f..7f33534 100644 --- a/examples/test-node.js +++ b/examples/test-node.js @@ -4,7 +4,7 @@ import { geolocation, coordinates, data, - LanguageGeocoding, + Language, } from '../dist/maptiler-client.mjs'; // For this examople to work, you must bring your own node-compatible fetch, @@ -12,14 +12,14 @@ import { // import fetch from 'node-fetch'; // config.fetch = fetch; -config.apiKey = 'YOUR_MAPTILER_CLOUD_API_KEY'; +config.apiKey = 'YOUR_API_KEY'; async function testGeocoding() { - // const result1 = await geocoding.forward('bordeaux', {language: [LanguageGeocoding.AUTO, LanguageGeocoding.ENGLISH]}); - // console.log(result1); + const result1 = await geocoding.forward('bordeaux', {language: [Language.AUTO, Language.ENGLISH]}); + console.log(result1); - const result2 = await geocoding.reverse([6.249638, 46.402056], {language: ['es', 'en']}); - console.log(result2); + // const result2 = await geocoding.reverse([6.249638, 46.402056], {language: ['es', 'en']}); + // console.log(result2); } async function testGeolocation() { diff --git a/package-lock.json b/package-lock.json index 0852fa0..6df56d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "@maptiler/client", - "version": "1.8.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@maptiler/client", - "version": "1.8.0", + "version": "2.0.0", "license": "BSD-3-Clause", "dependencies": { - "@rollup/pluginutils": "^5.0.5", - "@types/geojson": "^7946.0.10", "quick-lru": "^7.0.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/pluginutils": "^5.0.5", + "@types/geojson": "^7946.0.10", "@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/parser": "^6.9.0", "eslint": "^8.52.0", @@ -847,6 +847,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "dev": true, "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -1241,12 +1242,14 @@ "node_modules/@types/estree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "dev": true }, "node_modules/@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.14", @@ -2467,7 +2470,8 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true }, "node_modules/esutils": { "version": "2.0.3", @@ -3574,6 +3578,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -3931,7 +3936,7 @@ "version": "4.6.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", - "devOptional": true, + "dev": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -5596,6 +5601,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "dev": true, "requires": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -5809,12 +5815,14 @@ "@types/estree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "dev": true }, "@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true }, "@types/json-schema": { "version": "7.0.14", @@ -6675,7 +6683,8 @@ "estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true }, "esutils": { "version": "2.0.3", @@ -7499,7 +7508,8 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true }, "pkg-types": { "version": "1.0.3", @@ -7750,7 +7760,7 @@ "version": "4.6.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==", - "devOptional": true, + "dev": true, "requires": { "@rollup/rollup-android-arm-eabi": "4.6.1", "@rollup/rollup-android-arm64": "4.6.1", diff --git a/package.json b/package.json index 36de5bc..fb84b00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@maptiler/client", - "version": "1.8.1", + "version": "2.0.0", "description": "Javascript & Typescript wrapper to MapTiler Cloud API", "module": "dist/maptiler-client.mjs", "types": "dist/maptiler-client.d.ts", @@ -56,6 +56,8 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/pluginutils": "^5.0.5", + "@types/geojson": "^7946.0.10", "@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/parser": "^6.9.0", "eslint": "^8.52.0", @@ -71,9 +73,7 @@ "typedoc": "^0.25.2", "typedoc-plugin-markdown": "^3.16.0", "typescript": "^5.2.2", - "vitest": "^0.34.6", - "@rollup/pluginutils": "^5.0.5", - "@types/geojson": "^7946.0.10" + "vitest": "^0.34.6" }, "dependencies": { "quick-lru": "^7.0.0" diff --git a/readme.md b/readme.md index 5351567..411adad 100644 --- a/readme.md +++ b/readme.md @@ -96,14 +96,16 @@ The same option object as the forward geocoding can be provided. Read more about reverse geocoding on our [official documentation](https://docs.maptiler.com/client-js/geocoding/#reverse). ### Language -For both *forward* and *reverse* geocoding, this library provides a list of supported languages as shorthands to [ISO language codes](https://en.wikipedia.org/wiki/ISO_639-1). The result will be provided in multiple languages if the `language` options is an array: +For both *forward* and *reverse* geocoding, this library provides a list of supported languages as shorthands that include [ISO language codes](https://en.wikipedia.org/wiki/ISO_639-1). The result will be provided in multiple languages if the `language` options is an array: ```ts -const result = await maptilerClient.geocoding.forward('paris', {language: [maptilerClient.geocoding.languages.SPANISH, maptilerClient.geocoding.languages.KOREAN]}) +const result = await maptilerClient.geocoding.forward('paris', {language: [maptilerClient.Language.SPANISH, maptilerClient.geocoding.Language.KOREAN]}) ``` The special language `AUTO` will detect the platform/browser preferred language. +If the language is not specified as options, MapTiler Cloud will use the `Accept-language` from the HTTP header of the request. The language seleted this way is generaly similar to the `Language.AUTO` mode, but can still differ in some cases ([read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)). + ## 🕵️‍♂️ Geolocation The geolocation service provides location informations of a visitor using its IP address. diff --git a/src/index.ts b/src/index.ts index e9c920b..816045d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { BBox, Position, LineString, MultiLineString } from "geojson"; +import type { BBox, Position, LineString, MultiLineString } from "geojson"; export type { BBox, Position, LineString, MultiLineString }; export * from "./config"; export * from "./language"; @@ -13,3 +13,4 @@ export * from "./services/math"; export * from "./services/elevation"; export * from "./tiledecoding"; export * from "./misc"; +export * from "./language"; diff --git a/src/language.ts b/src/language.ts index 4ecffda..d5461db 100644 --- a/src/language.ts +++ b/src/language.ts @@ -1,87 +1,1145 @@ +export type LanguageInfo = { + /** + * Two letter ISO code, such as `"en"` for English language. + * Can be `null` if the language is a flag to be evaluated at runtime, + * as it is the case for some "modes". + */ + code: string | null; + + /** + * The full OSM language flag, such as `"name:en"` for the English language. + * Can also be a non-OSM flag if the language needs to be evaluated at runtime, such as `"auto"`, + * as it is the case for some "modes". + */ + flag: string; + + /** + * English name of the language. + */ + name: string; + + /** + * Whether the language leverages only the latin charsets. + */ + latin: boolean; + + /** + * Some language descriptions corresponds to "modes" rather than to actual languages. + * For instance the "visitor" mode consists in displaying bylingual labels. + */ + isMode: boolean; + + /** + * Whether the language is compatible with the geocoding API + */ + geocoding: boolean; +}; + /** - * Languages. Note that not all the languages of this list are available but the compatibility list may be expanded in the future. + * The complete list of languages */ -const LanguageGeocoding = { - AUTO: "auto", - ALBANIAN: "sq", - ARABIC: "ar", - ARMENIAN: "hy", - AZERBAIJANI: "az", - BELORUSSIAN: "be", - BOSNIAN: "bs", - BRETON: "br", - BULGARIAN: "bg", - CATALAN: "ca", - CHINESE: "zh", - CROATIAN: "hr", - CZECH: "cs", - DANISH: "da", - DUTCH: "nl", - ENGLISH: "en", - ESPERANTO: "eo", - ESTONIAN: "et", - FINNISH: "fi", - FRENCH: "fr", - FRISIAN: "fy", - GEORGIAN: "ka", - GERMAN: "de", - GREEK: "el", - HEBREW: "he", - HUNGARIAN: "hu", - ICELANDIC: "is", - IRISH: "ga", - ITALIAN: "it", - JAPANESE: "ja", - KANNADA: "kn", - KAZAKH: "kk", - KOREAN: "ko", - ROMAN_LATIN: "la", - LATVIAN: "lv", - LITHUANIAN: "lt", - LUXEMBOURGISH: "lb", - MACEDONIAN: "mk", - MALTESE: "mt", - NORWEGIAN: "no", - POLISH: "pl", - PORTUGUESE: "pt", - ROMANIAN: "ro", - ROMANSH: "rm", - RUSSIAN: "ru", - SCOTTISH_GAELIC: "gd", - SERBIAN_CYRILLIC: "sr", - SLOVAK: "sk", - SLOVENE: "sl", - SPANISH: "es", - SWEDISH: "sv", - THAI: "th", - TURKISH: "tr", - UKRAINIAN: "uk", - WELSH: "cy", -}; +export const Language = { + /** + * Language mode to display the labels in the end user's device language. + */ + AUTO: { + code: null, + flag: "auto", + name: "Auto", + latin: false, + isMode: true, + geocoding: true, + } as LanguageInfo, + + /** + * The OSM language using latin script. MapTiler discourages its use as a primary language setting due to the lack of actual linguistic specificity, + * though it can be an handy fallback. This is not to be confused with the "Classical Latin" language, which is available under the tag `.CLASSICAL_LATIN`. + */ + LATIN: { + code: "latin", + flag: "name:latin", + name: "Latin", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * The OSM language using non-latin script. MapTiler discourages its use as a primary language setting due to the lack of actual linguistic specificity, + * though it can be an handy fallback. + */ + NON_LATIN: { + code: "nonlatin", + flag: "name:nonlatin", + name: "Non Latin", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Using the local language generaly (but not always) means that every labels of a given region will use the dominant local language. + */ + LOCAL: { + code: null, + flag: "name", + name: "Local", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Amharic language + */ + AMHARIC: { + code: "am", + flag: "name:am", + name: "Amharic", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Arabic language (right-to-left script) + */ + ARABIC: { + code: "ar", + flag: "name:ar", + name: "Arabic", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Azerbaijani language + */ + AZERBAIJANI: { + code: "az", + flag: "name:az", + name: "Azerbaijani", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Belarusian langauge + */ + BELARUSIAN: { + code: "be", + flag: "name:be", + name: "Belarusian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Bulgarian language + */ + BULGARIAN: { + code: "bg", + flag: "bg", + name: "Bulgarian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Bengali language + */ + BENGALI: { + code: "bn", + flag: "name:bn", + name: "Bengali", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Breton language + */ + BRETON: { + code: "br", + flag: "name:br", + name: "Breton", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Bosnian language + */ + BOSNIAN: { + code: "bs", + flag: "name:bs", + name: "Bosnian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Catalan language + */ + CATALAN: { + code: "ca", + flag: "name:ca", + name: "Catalan", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Chinese language + */ + CHINESE: { + code: "zh", + flag: "name:zh", + name: "Chinese", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Traditional Chinese language + */ + TRADITIONAL_CHINESE: { + code: "zh-Hant", + flag: "name:zh-Hant", + name: "Chinese (traditional)", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Simplified Chinese language + */ + SIMPLIFIED_CHINESE: { + code: "zh-Hans", + flag: "name:zh-Hans", + name: "Chinese (simplified)", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Corsican language + */ + CORSICAN: { + code: "co", + flag: "name:co", + name: "Corsican", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Czech language + */ + CZECH: { + code: "cs", + flag: "name:cs", + name: "Czech", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Welsh language + */ + WELSH: { + code: "cy", + flag: "name:cy", + name: "WELSH", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Danish language + */ + DANISH: { + code: "da", + flag: "name:da", + name: "Danish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * German language + */ + GERMAN: { + code: "de", + flag: "name:de", + name: "German", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Greek language + */ + GREEK: { + code: "el", + flag: "name:el", + name: "Greek", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * English language + */ + ENGLISH: { + code: "en", + flag: "name:en", + name: "English", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Esperanto language + */ + ESPERANTO: { + code: "eo", + flag: "name:eo", + name: "Esperanto", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Spanish language + */ + SPANISH: { + code: "es", + flag: "name:es", + name: "Spanish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Estonian language + */ + ESTONIAN: { + code: "et", + flag: "name:et", + name: "Estonian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Basque language + */ + BASQUE: { + code: "eu", + flag: "name:eu", + name: "Basque", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Finnish language + */ + FINNISH: { + code: "fi", + flag: "name:fi", + name: "Finnish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * French language + */ + FRENCH: { + code: "fr", + flag: "name:fr", + name: "French", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Frisian language + */ + FRISIAN: { + code: "fy", + flag: "name:fy", + name: "Frisian (West)", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Irish language + */ + IRISH: { + code: "ga", + flag: "name:ga", + name: "Irish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Scottish Gaelic language + */ + SCOTTISH_GAELIC: { + code: "gd", + flag: "name:gd", + name: "Scottish Gaelic", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Hebrew language (right-to-left non-latin script) + */ + HEBREW: { + code: "he", + flag: "name:he", + name: "Hebrew", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Hindi language + */ + HINDI: { + code: "hi", + flag: "name:hi", + name: "Hindi", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Croatian language + */ + CROATIAN: { + code: "hr", + flag: "name:hr", + name: "Croatian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Hungarian language + */ + HUNGARIAN: { + code: "hu", + flag: "name:hu", + name: "Hungarian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Armenian language + */ + ARMENIAN: { + code: "hy", + flag: "name:hy", + name: "Armenian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Indonesian language + */ + INDONESIAN: { + code: "id", + flag: "name:id", + name: "Indonesian", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Icelandic language + */ + ICELANDIC: { + code: "is", + flag: "name:is", + name: "Icelandic", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Italian language + */ + ITALIAN: { + code: "it", + flag: "name:it", + name: "Italian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Japanese language + */ + JAPANESE: { + code: "ja", + flag: "name:ja", + name: "Japanese", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Japanese language in Hiragana form + */ + JAPANESE_HIRAGANA: { + code: "ja-Hira", + flag: "name:ja-Hira", + name: "Japanese Hiragana form", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Japanese language (latin script) + */ + JAPANESE_2018: { + code: "ja-Latn", + flag: "name:ja-Latn", + name: "Japanese (Latin 2018)", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Japanese language in Kana form (non-latin script) + */ + JAPANESE_KANA: { + code: "ja_kana", + flag: "name:ja_kana", + name: "Japanese (Kana)", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Japanse language, romanized (latin script) + */ + JAPANESE_LATIN: { + code: "ja_rm", + flag: "name:ja_rm", + name: "Japanese (Latin script)", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Georgian language + */ + GEORGIAN: { + code: "ka", + flag: "name:ka", + name: "Georgian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Kazakh language + */ + KAZAKH: { + code: "kk", + flag: "name:kk", + name: "Kazakh", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Kannada language + */ + KANNADA: { + code: "kn", + flag: "name:kn", + name: "Kannada", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Korean language + */ + KOREAN: { + code: "ko", + flag: "name:ko", + name: "Korean", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Korean language (latin script) + */ + KOREAN_LATIN: { + code: "ko-Latn", + flag: "name:ko-Latn", + name: "Korean (Latin script)", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, -const languageCodeSet = new Set(Object.values(LanguageGeocoding)); + /** + * Kurdish language + */ + KURDISH: { + code: "ku", + flag: "name:ku", + name: "Kurdish", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, -type Values = T[keyof T]; + /** + * Classical Latin language + */ + CLASSICAL_LATIN: { + code: "la", + flag: "name:la", + name: "Latin", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Luxembourgish language + */ + LUXEMBOURGISH: { + code: "lb", + flag: "name:lb", + name: "Luxembourgish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Lithuanian language + */ + LITHUANIAN: { + code: "lt", + flag: "name:lt", + name: "Lithuanian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Latvian language + */ + LATVIAN: { + code: "lv", + flag: "name:lv", + name: "Latvian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Macedonian language + */ + MACEDONIAN: { + code: "mk", + flag: "name:mk", + name: "Macedonian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Malayalm language + */ + MALAYALAM: { + code: "ml", + flag: "name:ml", + name: "Malayalam", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Maltese language + */ + MALTESE: { + code: "mt", + flag: "name:mt", + name: "Maltese", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Dutch language + */ + DUTCH: { + code: "nl", + flag: "name:nl", + name: "Dutch", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Norwegian language + */ + NORWEGIAN: { + code: "no", + flag: "name:no", + name: "Norwegian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Occitan language + */ + OCCITAN: { + code: "oc", + flag: "name:oc", + name: "Occitan", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Polish language + */ + POLISH: { + code: "pl", + flag: "name:pl", + name: "Polish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Persian language + */ + PERSIAN: { + code: "fa", + flag: "name:fa", + name: "Persian", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Punjabi language + */ + PUNJABI: { + code: "pa", + flag: "name:pa", + name: "Punjabi", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Western Punjabi language + */ + WESTERN_PUNJABI: { + code: "pnb", + flag: "name:pnb", + name: "Western Punjabi", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Portuguese language + */ + PORTUGUESE: { + code: "pt", + flag: "name:pt", + name: "Portuguese", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Romansh language + */ + ROMANSH: { + code: "rm", + flag: "name:rm", + name: "Romansh", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Romanian language + */ + ROMANIAN: { + code: "ro", + flag: "name:ro", + name: "Romanian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Russian language + */ + RUSSIAN: { + code: "ru", + flag: "name:ru", + name: "Russian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Slovak language + */ + SLOVAK: { + code: "sk", + flag: "name:sk", + name: "Slovak", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Slovene language + */ + SLOVENE: { + code: "sl", + flag: "name:sl", + name: "Slovene", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Albanian language + */ + ALBANIAN: { + code: "sq", + flag: "name:sq", + name: "Albanian", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Serbian language (cyrillic script) + */ + SERBIAN_CYRILLIC: { + code: "sr", + flag: "name:sr", + name: "Serbian (Cyrillic script)", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Serbian language (latin script) + */ + SERBIAN_LATIN: { + code: "sr-Latn", + flag: "name:sr-Latn", + name: "Serbian (Latin script)", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Swedish language + */ + SWEDISH: { + code: "sv", + flag: "name:sv", + name: "Swedish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Tamil language + */ + TAMIL: { + code: "ta", + flag: "name:ta", + name: "Tamil", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Telugu language + */ + TELUGU: { + code: "te", + flag: "name:te", + name: "Telugu", + latin: false, + isMode: false, + geocoding: false, + } as LanguageInfo, + + /** + * Thai language + */ + THAI: { + code: "th", + flag: "name:th", + name: "Thai", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Turkish language + */ + TURKISH: { + code: "tr", + flag: "name:tr", + name: "Turkish", + latin: true, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Ukrainian language + */ + UKRAINIAN: { + code: "uk", + flag: "name:uk", + name: "Ukrainian", + latin: false, + isMode: false, + geocoding: true, + } as LanguageInfo, + + /** + * Vietnamese language (latin script) + */ + VIETNAMESE: { + code: "vi", + flag: "name:vi", + name: "Vietnamese (Latin script)", + latin: true, + isMode: false, + geocoding: false, + } as LanguageInfo, +} as const; /** - * Built-in languages values as strings + * Get language infos from a provided language key, the key being the no-whitespace capital name. + * By default, the language dictionnary to look into is the one defined in this library, but another one could be provided + * Returns `null` if not found. */ -type LanguageGeocodingString = Values; +export function getLanguageInfoFromKey( + languageKey: string, + languageDictionnary: { [k: string]: LanguageInfo } = Language, +): LanguageInfo | null { + if (languageKey in languageDictionnary) { + return languageKey[languageKey]; + } + return null; +} + +/** + * Get the language info from a provided 2-character iso code. + * By default, the language dictionnary to look into is the one defined in this library, but another one could be provided + * Returns `null` if not found. + */ +export function getLanguageInfoFromCode( + languageCode: string, + languageDictionnary: { [k: string]: LanguageInfo } = Language, +): LanguageInfo | null { + for (const lang of Object.values(languageDictionnary)) { + if (lang.code === languageCode) { + return lang; + } + } + return null; +} -function getAutoLanguageGeocoding(): LanguageGeocodingString { +/** + * Get the language info from a language flag (eg. `"name:en"`). + * This is also handy to check is a given language flag is a supported language. + * By default, the language dictionnary to look into is the one defined in this library, but another one could be provided + * Returns `null` if not found. + */ +export function getLanguageInfoFromFlag( + languageFlag: string, + languageDictionnary: { [k: string]: LanguageInfo } = Language, +): LanguageInfo | null { + for (const lang of Object.values(languageDictionnary)) { + if (lang.flag === languageFlag) { + return lang; + } + } + return null; +} + +/** + * Get the default language of the device, as a LanguageInfo object. + */ +export function getAutoLanguage(): LanguageInfo { if (typeof navigator === "undefined") { - return Intl.DateTimeFormat() - .resolvedOptions() - .locale.split("-")[0] as LanguageGeocodingString; + const code = Intl.DateTimeFormat().resolvedOptions().locale.split("-")[0]; + const langInfo = getLanguageInfoFromCode(code); + return langInfo ?? Language.ENGLISH; } const canditatelangs = Array.from( new Set(navigator.languages.map((l) => l.split("-")[0])), - ).filter((l) => languageCodeSet.has(l as LanguageGeocodingString)); + ) + .map((code) => getLanguageInfoFromCode(code)) + .filter((li) => li); - return canditatelangs.length - ? (canditatelangs[0] as LanguageGeocodingString) - : LanguageGeocoding.ENGLISH; + return canditatelangs[0] ?? Language.ENGLISH; } -export { LanguageGeocoding, LanguageGeocodingString, getAutoLanguageGeocoding }; +export function isLanguageInfo(obj: unknown): obj is LanguageInfo { + return ( + obj !== null && + typeof obj === "object" && + "code" in obj && + "flag" in obj && + "name" in obj && + "latin" in obj && + "isMode" in obj && + "geocoding" in obj && + (typeof obj.code === "string" || obj.code === null) && + typeof obj.flag === "string" && + typeof obj.name === "string" && + typeof obj.latin === "boolean" && + typeof obj.isMode === "boolean" && + typeof obj.geocoding === "boolean" + ); +} + +/** + * By default, the language dictionnary to look into is the one defined in this library, but another one could be provided + */ +export function toLanguageInfo( + lang: LanguageInfo | string, + languageDictionnary: { [k: string]: LanguageInfo } = Language, +): LanguageInfo | null { + // Could be directly an object of type LanguageInfo + if (isLanguageInfo(lang)) { + // Yet we want to make sure the provided languageInfo obj is not corrupted or incomplete, + // so we ask for the equivalent original: + return getLanguageInfoFromFlag(lang.flag, languageDictionnary); // possibly returns null, which is fine. + } + + // If it's not even a string, then it does not represent a language + if (typeof lang !== "string") { + return null; + } + + return ( + getLanguageInfoFromKey(lang, languageDictionnary) || + getLanguageInfoFromCode(lang, languageDictionnary) || + getLanguageInfoFromFlag(lang, languageDictionnary) || + null + ); +} + +/** + * Tells if two languages are the same, even though possibly provided under different forms. + * Note: this is not comparing object references, but values. + */ +export function areSameLanguages( + langA: string | LanguageInfo, + langB: string | LanguageInfo, + languageDictionnary: { [k: string]: LanguageInfo } = Language, +): boolean { + const langAObj = toLanguageInfo(langA, languageDictionnary); + const langBObj = toLanguageInfo(langB, languageDictionnary); + + return langAObj && langBObj && langAObj.flag === langBObj.flag; +} diff --git a/src/services/geocoding.ts b/src/services/geocoding.ts index fc7d8ce..fa2f067 100644 --- a/src/services/geocoding.ts +++ b/src/services/geocoding.ts @@ -1,12 +1,15 @@ -import { BBox, Feature, Geometry, Position } from "geojson"; +import type { BBox, Feature, Geometry, Position } from "geojson"; import { callFetch } from "../callFetch"; import { config } from "../config"; import { defaults } from "../defaults"; import { - getAutoLanguageGeocoding, - LanguageGeocoding, - LanguageGeocodingString, + type LanguageInfo, + getAutoLanguage, + getLanguageInfoFromCode, + isLanguageInfo, + Language, + getLanguageInfoFromFlag, } from "../language"; import { ServiceError } from "./ServiceError"; @@ -19,7 +22,7 @@ export type LanguageGeocodingOptions = { /** * Prefer results in specific language. It’s possible to specify multiple values. */ - language?: LanguageGeocodingString | Array; + language?: string | Array | LanguageInfo | Array; }; export type CommonForwardAndReverseGeocodingOptions = @@ -260,21 +263,42 @@ function addLanguageGeocodingOptions( ) { const { language } = options; - if (language == undefined) { + if (language === undefined) { return; } - const languages = Array.from( - new Set( - (Array.isArray(language) ? language : [language]).map((lang) => - lang === LanguageGeocoding.AUTO ? getAutoLanguageGeocoding() : lang, - ), - ), - ).join(","); + // Making it an array of language codes + const languageCodes = (Array.isArray(language) ? language : [language]) + .map((elem) => toValidGeocodingLanguageCode(elem)) + .filter((elem) => elem); // removing the nulls + + const languages = Array.from(new Set(languageCodes)).join(","); searchParams.set("language", languages); } +function toValidGeocodingLanguageCode( + lang: string | LanguageInfo, +): string | null { + let langInfo: LanguageInfo | null = null; + + if (lang === Language.AUTO.flag) { + // equal to the string "auto" + langInfo = getAutoLanguage(); + } else if (typeof lang === "string") { + langInfo = getLanguageInfoFromCode(lang); + } else if (isLanguageInfo(lang)) { + langInfo = + lang.flag === Language.AUTO.flag + ? getAutoLanguage() + : getLanguageInfoFromFlag(lang.flag); + } + + if (!langInfo) return null; + if (langInfo.geocoding) return langInfo.code; + return null; +} + function addCommonForwardAndReverseGeocodingOptions( searchParams: URLSearchParams, options: CommonForwardAndReverseGeocodingOptions, @@ -283,15 +307,15 @@ function addCommonForwardAndReverseGeocodingOptions( searchParams.set("key", apiKey ?? config.apiKey); - if (limit != undefined) { + if (limit !== undefined) { searchParams.set("limit", String(limit)); } - if (types != undefined) { + if (types !== undefined) { searchParams.set("types", types.join(",")); } - if (excludeTypes != undefined) { + if (excludeTypes !== undefined) { searchParams.set("excludeTypes", String(excludeTypes)); } @@ -306,26 +330,26 @@ function addForwardGeocodingOptions( const { bbox, proximity, country, fuzzyMatch, autocomplete } = options; - if (bbox != undefined) { + if (bbox !== undefined) { searchParams.set("bbox", bbox.join(",")); } - if (proximity != undefined) { + if (proximity !== undefined) { searchParams.set( "proximity", proximity === "ip" ? proximity : proximity.join(","), ); } - if (country != undefined) { + if (country !== undefined) { searchParams.set("country", country.join(",")); } - if (fuzzyMatch != undefined) { + if (fuzzyMatch !== undefined) { searchParams.set("fuzzyMatch", fuzzyMatch ? "true" : "false"); } - if (autocomplete != undefined) { + if (autocomplete !== undefined) { searchParams.set("autocomplete", autocomplete ? "true" : "false"); } } @@ -488,7 +512,6 @@ const geocoding = { reverse, byId, batch, - language: LanguageGeocoding, }; export { geocoding };