diff --git a/README.md b/README.md
index e59ee2360..2fe290a99 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,32 @@ Must be set to `true` if a `catalogUrl` is not given as otherwise you won't be a
You can list additional domains (e.g. `example.com`) that private data is sent to, e.g. authentication data.
+### detectLocaleFromBrowser
+
+If set to `true`, tries to detect the preferred language of the user from the Browser.
+Otherwise, defaults to the language set for `locale`.
+
+### locale
+
+The default language to use for STAC Browser, defaults to `en` (English).
+The language given here must be present in `supportedLocales`.
+
+### fallbackLocale
+
+The language to use if individual phrases are not available in the default language, defaults to `en` (English).
+The language given here must be present in `supportedLocales`.
+
+### supportedLocales
+
+A list of languages to show in the STAC Browser UI.
+The languages given here must have a corresponding JS and JSON file in the `src/locales` folder,
+e.g. provide `en` (English) for the files in `src/locales/en`.
+
+In CLI, please provide the languages separated by a space, e.g. `--supportedLocales en de fr it`
+
+Please note that only left-to-right languages have been tested.
+I'd need help to test support for right-to-left languages.
+
### stacLint
***experimental***
@@ -230,6 +256,7 @@ There are four options you can set in the `authConfig` object:
* `key` (string): The query string parameter name or the HTTP header name respecively.
* `formatter` (function|null): You can optionally specify a formatter for the query string value or HTTP header value respectively. If not given, the token is provided as provided by the user.
* `description` (string|null): Optionally a description that is shown to the user. This should explain how the token can be obtained for example. CommonMark is allowed.
+ **Note:** You can leave the description empty in the config file and instead provide a localized string with the key `authConfig` -> `description` in the file for custom phrases (`src/locales/custom.js`).
Please note that this option can only be provided through a config file and is not available via CLI.
diff --git a/config.js b/config.js
index 4717c27d5..e675a9b60 100644
--- a/config.js
+++ b/config.js
@@ -3,6 +3,16 @@ module.exports = {
catalogTitle: "STAC Browser",
allowExternalAccess: true, // Must be true if catalogUrl is not given
allowedDomains: [],
+ detectLocaleFromBrowser: true,
+ locale: "en",
+ fallbackLocale: "en",
+ supportedLocales: [
+ "en",
+ "en-US",
+ "de",
+ "fr-CA",
+ "fr-FR"
+ ],
useTileLayerAsFallback: true,
tileSourceTemplate: null,
displayGeoTiffByDefault: false,
diff --git a/helpers/fields_locales.js b/helpers/fields_locales.js
new file mode 100644
index 000000000..f92ab0ca4
--- /dev/null
+++ b/helpers/fields_locales.js
@@ -0,0 +1,73 @@
+const Fields = require('@radiantearth/stac-fields/fields-normalized.json');
+const HardCodedFields = require("./fields_locales.json");
+const fs = require('fs');
+
+const translatable = ["label", "explain", "unit"];
+const iterable = ["items", "properties"];
+const dest_file = "src/locales/en/fields.json";
+
+function ignore(text, path, otherPath) {
+ if (path.endsWith(".unit")) {
+ return true;
+ }
+ if (path.includes(".id.") || path.includes(".name.") || path.includes(".title.") || path.includes(".description.")) {
+ return true;
+ }
+ if (path.includes(".href") || path.includes(".roles.")) {
+ return true;
+ }
+ if (path.includes(".average.") || path.includes(".minimum.") || path.includes(".maximum.") || path.includes(".stddev.")) {
+ return true;
+ }
+ if (path.includes("cube:") && otherPath.includes("cube:")) {
+ return true;
+ }
+ if (path.includes("links.type.") && otherPath.includes("assets.type.")) {
+ return true;
+ }
+ if (path.includes(".mgrs.")) {
+ return true;
+ }
+ return false;
+}
+
+function addText(locales, text, path) {
+ if (locales[text] && !ignore(text, path, locales[text])) {
+ console.warn(`Potential conflict between '${path}' and '${locales[text]}'`);
+ }
+ locales[text] = path;
+}
+
+function findTexts(locales, fields, path) {
+ for(let name in fields) {
+ let field = fields[name];
+ if (field.alias) {
+ continue;
+ }
+
+ translatable.forEach(key => field[key] && addText(locales, field[key], `${path}.${name}.${key}`));
+ iterable.forEach(key => field[key] && findTexts(locales, field[key], `${path}.${name}.${key}`));
+ if (field.mapping) {
+ Object.entries(field.mapping).forEach(([key, text]) => addText(locales, text, `${path}.${name}.mapping.${key}`));
+ }
+ }
+}
+
+function writeToFile(file, locales) {
+ const data = {};
+ Object.keys(locales).sort().forEach(key => data[key] = key);
+ const json = JSON.stringify(data, null, 2);
+ fs.writeFileSync(file, json);
+}
+
+function generateLocales() {
+ const locales = {};
+ HardCodedFields.forEach(text => addText(locales, text, "hardcoded"));
+ const types = ["assets", "extensions", "links", "metadata"];
+ types.forEach(type => findTexts(locales, Fields[type], type));
+ writeToFile(dest_file, locales);
+}
+
+console.log(`Generating fields locale file`);
+generateLocales();
+console.log(`Saved fields locale file to ${dest_file}`);
\ No newline at end of file
diff --git a/helpers/fields_locales.json b/helpers/fields_locales.json
new file mode 100644
index 000000000..0351c2f5a
--- /dev/null
+++ b/helpers/fields_locales.json
@@ -0,0 +1,87 @@
+[
+ "n/a",
+ "none",
+
+ "Hashing algorithm:",
+
+ "Until {0}",
+ "{0} until present",
+
+ "8-bit integer",
+ "16-bit integer",
+ "32-bit integer",
+ "64-bit integer",
+ "unsigned 8-bit integer",
+ "unsigned 16-bit integer",
+ "unsigned 32-bit integer",
+ "unsigned 64-bit integer",
+ "16-bit float",
+ "32-bit float",
+ "64-bit float",
+ "16-bit complex integer",
+ "32-bit complex integer",
+ "32-bit complex float",
+ "64-bit complex float",
+ "non-standard",
+
+ "MGRS",
+ "Military Grid Reference System",
+ "UTM Zone",
+ "Latitude Band",
+ "Square Identifier",
+ "Easting",
+ "Northing",
+ "MODIS Sinusoidal Tile Grid",
+ "Horizontal",
+ "Vertical",
+ "WRS-1",
+ "Worldwide Reference System 1",
+ "WRS-2",
+ "Worldwide Reference System 2",
+ "Path",
+ "Row",
+ "DOQ",
+ "Digital Orthophoto Quadrangle",
+ "Quadrangle",
+ "DOQQ",
+ "Digital Orthophoto Quarter Quadrangle",
+ "North",
+ "East",
+ "South",
+ "West",
+ "Quarter",
+ "Maxar ARD Tile Grid",
+ "Quadkey",
+ "EASE-DGGS",
+ "Level",
+ "Level 0 row cell",
+ "Level 0 column cell",
+ "Fraction of level {i} row cell",
+ "Fraction of level {i} column cell",
+
+ "Cloud-Optimized GeoTIFF image",
+ "GeoTIFF image",
+ "TIFF image",
+ "JPEG 2000 image",
+ "PNG image",
+ "GIF image",
+ "JPEG image",
+ "WebP image",
+ "Bitmap",
+ "Bitmap image",
+ "SVG vector image",
+ "Comma-separated values (CSV)",
+ "Newline Delimited JSON",
+ "HTML (Website)",
+ "Text",
+ "Text document",
+ "Markdown document",
+ "PDF document",
+ "ZIP archive",
+ "GZIP archive",
+ "Meta Raster Format",
+ "Binary",
+ "Binary file",
+ "Cloud-Optimized Point Cloud (LASzip)",
+ "Font"
+]
\ No newline at end of file
diff --git a/package.json b/package.json
index 172e39b40..6f130febf 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,9 @@
"scripts": {
"start": "vue-cli-service serve",
"build": "vue-cli-service build --report",
- "lint": "vue-cli-service lint"
+ "lint": "vue-cli-service lint",
+ "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.(js|json)\"",
+ "i18n:fields": "node helpers/fields_locales.js"
},
"bugs": {
"url": "https://github.com/radiantearth/stac-browser/issues"
@@ -30,6 +32,7 @@
"license": "ISC",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.9",
+ "@musement/iso-duration": "^1.0.0",
"@radiantearth/stac-fields": "1.0.0-beta.25",
"@radiantearth/stac-migrate": "~1.2.0",
"axios": "^1.2.0",
@@ -38,12 +41,14 @@
"commonmark": "^0.29.3",
"core-js": "^3.6.5",
"leaflet": "^1.8.0",
+ "locale-id": "^1.1.2",
"node-polyfill-webpack-plugin": "^2.0.0",
"remove-markdown": "^0.5.0",
"stac-layer": "^0.15.0",
"urijs": "^1.19.11",
"v-clipboard": "^2.2.3",
"vue": "^2.6.12",
+ "vue-i18n": "^8.28.2",
"vue-multiselect": "^2.1.6",
"vue-read-more-smooth": "^0.1.8",
"vue-router": "^3.2.0",
@@ -64,6 +69,7 @@
"eslint-plugin-vue": "^8.7.1",
"sass": "^1.26.5",
"sass-loader": "^13.2.0",
+ "vue-cli-plugin-i18n": "~2.3.1",
"vue-template-compiler": "^2.6.12"
},
"browserslist": [
diff --git a/src/StacBrowser.vue b/src/StacBrowser.vue
index 4633da426..0ce970211 100644
--- a/src/StacBrowser.vue
+++ b/src/StacBrowser.vue
@@ -1,12 +1,12 @@
Providers
+ {{ $tc('providers.title', count) }}
- in
-