Skip to content

Commit

Permalink
Add first i18n infrastructure and translate utils and about-dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
cdauth committed Mar 31, 2024
1 parent 0ca3fa9 commit f4c6a2f
Show file tree
Hide file tree
Showing 29 changed files with 680 additions and 62 deletions.
1 change: 0 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ module.exports = {
"import/no-extraneous-dependencies": ["error"],
"@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }],
"import/no-named-as-default": ["warn"],
"import/no-named-as-default-member": ["warn"],
"import/no-duplicates": ["warn"],
"import/namespace": ["error"],
"import/default": ["error"],
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"facilmap-utils": "workspace:^",
"file-saver": "^2.0.5",
"hammerjs": "^2.0.8",
"i18next": "^23.10.1",
"jquery": "^3.7.1",
"leaflet": "^1.9.4",
"leaflet-draggable-lines": "^2.0.0",
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/i18n/de.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const messagesDe = {
"about-dialog": {
"header": `Über FacilMap {{version}}`,
"license-text": `{{facilmap}} is unter der {{license}} verfügbar.`,
"license-text-facilmap": `FacilMap`,
"license-text-license": `GNU Affero General Public License, Version 3`,
"issues-text": `Bitte melden Sie Fehler und Verbesserungsvorschläge auf {{tracker}}.`,
"issues-text-tracker": `GitHub`,
"help-text": `Wenn Sie Fragen haben, schauen Sie sich die {{documentation}} an, schreiben Sie ins {{discussions}} oder fragen im {{chat}}.`,
"help-text-documentation": `Dokumentation`,
"help-text-discussions": `Forum`,
"help-text-chat": `Matrix-Chat`,
"privacy-information": `Informationen zum Datenschutz`,
"map-data": `Kartendaten`,
"map-data-search": `Suche`,
"map-data-pois": `POIs`,
"map-data-directions": `Routenberechnung`,
"map-data-geoip": `GeoIP`,
"map-data-geoip-description": `Dieses Produkt enthält GeoLine2-Daten von Maxmind, verfügbar unter {{maxmind}}.`,
"attribution-osm-contributors": `OSM-Mitwirkende`,
"programs-libraries": `Programme/Bibliotheken`,
"icons": `Symbole`
},

"modal-dialog": {
"close": "Schließen",
"cancel": "Abbrechen",
"save": "Speichern"
}
};

export default messagesDe;
32 changes: 32 additions & 0 deletions frontend/src/i18n/en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const messagesEn = {
"about-dialog": {
"header": `About FacilMap {{version}}`,
"license-text": `{{facilmap}} is available under the {{license}}.`,
"license-text-facilmap": `FacilMap`,
"license-text-license": `GNU Affero General Public License, Version 3`,
"issues-text": `If something does not work or you have a suggestion for improvement, please report on the {{tracker}}.`,
"issues-text-tracker": `issue tracker`,
"help-text": `If you have a question, please have a look at the {{documentation}}, raise a question in the {{discussions}} or ask in the {{chat}}.`,
"help-text-documentation": `documentation`,
"help-text-discussions": `discussion forum`,
"help-text-chat": `Matrix chat`,
"privacy-information": `Privacy information`,
"map-data": `Map data`,
"map-data-search": `Search`,
"map-data-pois": `POIs`,
"map-data-directions": `Directions`,
"map-data-geoip": `GeoIP`,
"map-data-geoip-description": `This product includes GeoLite2 data created by MaxMind, available from {{maxmind}}.`,
"attribution-osm-contributors": `OSM Contributors`,
"programs-libraries": `Programs/libraries`,
"icons": `Icons`
},

"modal-dialog": {
"close": "Close",
"cancel": "Cancel",
"save": "Save"
}
};

export default messagesEn;
71 changes: 54 additions & 17 deletions frontend/src/lib/components/about-dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import { computed } from "vue";
import ModalDialog from "./ui/modal-dialog.vue";
import { injectContextRequired, requireMapContext } from "./facil-map-context-provider/facil-map-context-provider.vue";
import { T, useI18n } from "../utils/i18n";
const { t } = useI18n();
const context = injectContextRequired();
const mapContext = requireMapContext(context);
Expand All @@ -18,21 +21,49 @@
});
const fmVersion = __FM_VERSION__;
</script>

<template>
<ModalDialog
:title="`About FacilMap ${fmVersion}`"
:title="t('about-dialog.header', { version: fmVersion })"
class="fm-about"
size="lg"
@hidden="emit('hidden')"
>
<p><a href="https://github.com/facilmap/facilmap" target="_blank"><strong>FacilMap</strong></a> is available under the <a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank">GNU Affero General Public License, Version 3</a>.</p>
<p>If something does not work or you have a suggestion for improvement, please report on the <a href="https://github.com/FacilMap/facilmap/issues" target="_blank">issue tracker</a>.</p>
<p>If you have a question, please have a look at the <a href="https://docs.facilmap.org/users/" target="_blank">documentation</a>, raise a question in the <a href="https://github.com/FacilMap/facilmap/discussions" target="_blank">discussion forum</a> or ask in the <a href="https://matrix.to/#/#facilmap:rankenste.in" target="_blank">Matrix chat</a>.</p>
<p><a href="https://docs.facilmap.org/users/privacy/" target="_blank">Privacy information</a></p>
<h4>Map data</h4>
<p>
<T k="about-dialog.license-text">
<template #facilmap>
<a href="https://github.com/facilmap/facilmap" target="_blank"><strong>{{t('about-dialog.license-text-facilmap')}}</strong></a>
</template>
<template #license>
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html" target="_blank">{{t('about-dialog.license-text-license')}}</a>
</template>
</T>
</p>
<p>
<T k="about-dialog.issues-text">
<template #tracker>
<a href="https://github.com/FacilMap/facilmap/issues" target="_blank">{{t('about-dialog.issues-text-tracker')}}</a>
</template>
</T>
</p>

<p>
<T k="about-dialog.help-text">
<template #documentation>
<a href="https://docs.facilmap.org/users/" target="_blank">{{t('about-dialog.help-text-documentation')}}</a>
</template>
<template #discussions>
<a href="https://github.com/FacilMap/facilmap/discussions" target="_blank">{{t('about-dialog.help-text-discussions')}}</a>
</template>
<template #chat>
<a href="https://matrix.to/#/#facilmap:rankenste.in" target="_blank">{{t('about-dialog.help-text-chat')}}</a>
</template>
</T>
</p>

<p><a href="https://docs.facilmap.org/users/privacy/" target="_blank">{{t('about-dialog.privacy-information')}}</a></p>
<h4>{{t('about-dialog.map-data')}}</h4>
<dl class="row">
<template v-for="layer in layers">
<template v-if="layer.options.attribution">
Expand All @@ -41,19 +72,25 @@
</template>
</template>

<dt class="col-sm-3">Search</dt>
<dd class="col-sm-9"><a href="https://nominatim.openstreetmap.org/" target="_blank">Nominatim</a> / <a href="https://www.openstreetmap.org/copyright" target="_blank">OSM Contributors</a></dd>
<dt class="col-sm-3">{{t('about-dialog.map-data-search')}}</dt>
<dd class="col-sm-9"><a href="https://nominatim.openstreetmap.org/" target="_blank">Nominatim</a> / <a href="https://www.openstreetmap.org/copyright" target="_blank">{{t('about-dialog.attribution-osm-contributors')}}</a></dd>

<dt class="col-sm-3">POIs</dt>
<dd class="col-sm-9"><a href="https://overpass-api.de/" target="_blank">Overpass API</a> / <a href="https://www.openstreetmap.org/copyright" target="_blank">OSM Contributors</a></dd>
<dt class="col-sm-3">{{t('about-dialog.map-data-pois')}}</dt>
<dd class="col-sm-9"><a href="https://overpass-api.de/" target="_blank">Overpass API</a> / <a href="https://www.openstreetmap.org/copyright" target="_blank">{{t('about-dialog.attribution-osm-contributors')}}</a></dd>

<dt class="col-sm-3">Directions</dt>
<dd class="col-sm-9"><a href="https://www.mapbox.com/api-documentation/#directions">Mapbox Directions API</a> / <a href="https://openrouteservice.org/">OpenRouteService</a> / <a href="https://www.openstreetmap.org/copyright" target="_blank">OSM Contributors</a></dd>
<dt class="col-sm-3">{{t('about-dialog.map-data-directions')}}</dt>
<dd class="col-sm-9"><a href="https://www.mapbox.com/api-documentation/#directions">Mapbox Directions API</a> / <a href="https://openrouteservice.org/">OpenRouteService</a> / <a href="https://www.openstreetmap.org/copyright" target="_blank">{{t('about-dialog.attribution-osm-contributors')}}</a></dd>

<dt class="col-sm-3">GeoIP</dt>
<dd class="col-sm-9">This product includes GeoLite2 data created by MaxMind, available from <a href="https://www.maxmind.com">https://www.maxmind.com</a>.</dd>
<dt class="col-sm-3">{{t('about-dialog.map-data-geoip')}}</dt>
<dd class="col-sm-9">
<T k="about-dialog.map-data-geoip-description">
<template #maxmind>
<a href="https://www.maxmind.com">https://www.maxmind.com</a>
</template>
</T>
</dd>
</dl>
<h4>Programs/libraries</h4>
<h4>{{t('about-dialog.programs-libraries')}}</h4>
<ul>
<li><a href="https://nodejs.org/" target="_blank">Node.js</a></li>
<li><a href="https://sequelize.org/" target="_blank">Sequelize</a></li>
Expand All @@ -72,7 +109,7 @@
<li><a href="https://expressjs.com/" target="_blank">Express</a></li>
<li><a href="https://vuepress.vuejs.org/" target="_blank">Vuepress</a></li>
</ul>
<h4>Icons</h4>
<h4>{{t('about-dialog.icons')}}</h4>
<ul>
<li><a href="https://github.com/twain47/Open-SVG-Map-Icons/" target="_blank">Open SVG Map Icons</a></li>
<li><a href="https://glyphicons.com/" target="_blank">Glyphicons</a></li>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/components/line-info/line-info.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { getZoomDestinationForLine } from "../../utils/zoom";
import RouteForm from "../route-form/route-form.vue";
import vTooltip from "../../utils/tooltip";
import { formatField, formatRouteMode, formatTime, normalizeLineName, round } from "facilmap-utils";
import { formatField, formatRouteTime, normalizeLineName, round } from "facilmap-utils";
import { computed, ref } from "vue";
import { useToasts } from "../ui/toasts/toasts.vue";
import { showConfirm } from "../ui/alert.vue";
Expand Down Expand Up @@ -160,7 +160,7 @@
<div class="fm-search-box-collapse-point" v-if="!isMoving">
<dl class="fm-search-box-dl">
<dt class="distance">Distance</dt>
<dd class="distance">{{round(line.distance, 2)}}&#x202F;km <span v-if="line.time != null">({{formatTime(line.time)}}&#x202F;h {{formatRouteMode(line.mode)}})</span></dd>
<dd class="distance">{{round(line.distance, 2)}}&#x202F;km <span v-if="line.time != null">({{formatRouteTime(line.time, line.mode)}})</span></dd>

<template v-if="line.ascent != null">
<dt class="elevation">Climb/drop</dt>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/components/route-form/route-form.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, markRaw, onBeforeUnmount, onMounted, ref, watch, watchEffect } from "vue";
import Icon from "../ui/icon.vue";
import { formatRouteMode, formatTime, isSearchId, normalizeMarkerName, round, splitRouteQuery } from "facilmap-utils";
import { formatRouteTime, isSearchId, normalizeMarkerName, round, splitRouteQuery } from "facilmap-utils";
import { useToasts } from "../ui/toasts/toasts.vue";
import type { ExportFormat, FindOnMapResult, SearchResult } from "facilmap-types";
import { getMarkerIcon, type HashQuery, MarkerLayer, RouteLayer } from "facilmap-leaflet";
Expand Down Expand Up @@ -670,7 +670,7 @@

<dl class="fm-search-box-dl">
<dt>Distance</dt>
<dd>{{round(routeObj.distance, 2)}}&#x202F;km <span v-if="routeObj.time != null">({{formatTime(routeObj.time)}}&#x202F;h {{formatRouteMode(routeObj.mode)}})</span></dd>
<dd>{{round(routeObj.distance, 2)}}&#x202F;km <span v-if="routeObj.time != null">({{formatRouteTime(routeObj.time, routeObj.mode)}})</span></dd>

<template v-if="routeObj.ascent != null">
<dt>Climb/drop</dt>
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/lib/components/ui/modal-dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import type { ThemeColour } from "../../utils/bootstrap";
import { useUnloadHandler } from "../../utils/utils";
import AttributePreservingElement from "./attribute-preserving-element.vue";
import { useI18n } from "../../utils/i18n";
const { t } = useI18n();
const props = withDefaults(defineProps<{
title?: string;
Expand Down Expand Up @@ -108,7 +111,7 @@
@click="modal.hide()"
type="button"
class="btn-close"
aria-label="Close"
:aria-label="t('modal-dialog.close')"
></button>
</div>
<div class="modal-body">
Expand All @@ -125,7 +128,7 @@
class="btn btn-secondary"
@click="modal.hide()"
:disabled="isSubmitting || props.isBusy"
>Cancel</button>
>{{t('modal-dialog.cancel')}}</button>

<button
type="submit"
Expand All @@ -134,7 +137,7 @@
:disabled="isSubmitting || props.isBusy"
>
<div v-if="isSubmitting" class="spinner-border spinner-border-sm"></div>
{{props.okLabel ?? (isCloseButton ? 'Close' : 'Save')}}
{{props.okLabel ?? (isCloseButton ? t('modal-dialog.close') : t('modal-dialog.save'))}}
</button>
</div>
</ValidatedForm>
Expand Down
73 changes: 73 additions & 0 deletions frontend/src/lib/utils/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/// <reference types="vite/client" />
import { type i18n } from "i18next";
import { defineComponent, ref } from "vue";
import messagesEn from "../../i18n/en";
import messagesDe from "../../i18n/de";
import { getRawI18n, onI18nReady } from "facilmap-utils";

const namespace = "facilmap-frontend";

onI18nReady((i18n) => {
i18n.addResourceBundle("en", namespace, messagesEn);
i18n.addResourceBundle("de", namespace, messagesDe);
});

if (import.meta.hot) {
import.meta.hot.accept("../../i18n/en", (m) => {
onI18nReady((i18n) => {
i18n.addResourceBundle("en", namespace, m!.default);
});
});

import.meta.hot.accept("../../i18n/de", (m) => {
onI18nReady((i18n) => {
i18n.addResourceBundle("de", namespace, m!.default);
});
});
}

const rerenderCounter = ref(0);
const rerender = () => {
rerenderCounter.value++;
};

onI18nReady((i18n) => {
i18n.store.on("added", rerender);
i18n.store.on("removed", rerender);
i18n.on("languageChanged", rerender);
i18n.on("loaded", rerender);
});

export function useI18n(): Pick<i18n, "t"> {
return {
t: new Proxy(getRawI18n().getFixedT(null, namespace), {
apply: (target, thisArg, argumentsList) => {
rerenderCounter.value;
return target.apply(thisArg, argumentsList as any);
}
})
};
}

export const T = defineComponent({
props: {
k: { type: String, required: true }
},
setup(props, { slots }) {
const i18n = useI18n();

return () => {
const mappedSlots = Object.entries(slots).map(([name, slot], i) => ({ name, placeholder: `%___SLOT_${i}___%`, slot }));
const placeholderByName = Object.fromEntries(mappedSlots.map(({ name, placeholder }) => [name, placeholder]));
const slotByPlaceholder = Object.fromEntries(mappedSlots.map(({ placeholder, slot }) => [placeholder, slot]));
const message = i18n.t(props.k, placeholderByName);
return message.split(/(%___SLOT_\d+___%)/g).map((v, i) => {
if (i % 2 === 0) {
return v;
} else {
return slotByPlaceholder[v]!();
}
});
};
}
});
13 changes: 8 additions & 5 deletions frontend/src/lib/utils/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { SelectedItem } from "./selection";
import type { FindOnMapLine, FindOnMapMarker, FindOnMapResult, Line, Marker, SearchResult } from "facilmap-types";
import type { Geometry } from "geojson";
import { isMapResult } from "./search";
import { decodeLonLatUrl, normalizeLineName, normalizeMarkerName } from "facilmap-utils";
import { decodeLonLatUrl, normalizeLineName, normalizeMarkerName, splitRouteQuery } from "facilmap-utils";
import type { ClientContext } from "../components/facil-map-context-provider/client-context";
import type { FacilMapContext } from "../components/facil-map-context-provider/facil-map-context";
import { requireClientContext, requireMapContext } from "../components/facil-map-context-provider/facil-map-context-provider.vue";
Expand Down Expand Up @@ -140,10 +140,13 @@ export async function openSpecialQuery(query: string, context: FacilMapContext,
const searchBoxContext = toRef(() => context.components.searchBox);
const routeFormTabContext = toRef(() => context.components.routeFormTab);

if(searchBoxContext.value && routeFormTabContext.value && query.match(/ to /i)) {
routeFormTabContext.value.setQuery(query, zoom, smooth);
searchBoxContext.value.activateTab(`fm${context.id}-route-form-tab`, { autofocus: true });
return true;
if(searchBoxContext.value && routeFormTabContext.value) {
const split = splitRouteQuery(query);
if (split.queries.length >= 2) {
routeFormTabContext.value.setQuery(query, zoom, smooth);
searchBoxContext.value.activateTab(`fm${context.id}-route-form-tab`, { autofocus: true });
return true;
}
}

const lonlat = decodeLonLatUrl(query);
Expand Down
4 changes: 4 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"cheerio": "^1.0.0-rc.12",
"compression": "^1.7.4",
"compressjs": "^1.0.3",
"cookie-parser": "^1.4.6",
"csv-stringify": "^6.4.6",
"dotenv": "^16.4.5",
"ejs": "^3.1.9",
Expand All @@ -53,6 +54,8 @@
"facilmap-types": "workspace:^",
"facilmap-utils": "workspace:^",
"find-cache-dir": "^5.0.0",
"i18next": "^23.10.1",
"i18next-http-middleware": "^3.5.0",
"lodash-es": "^4.17.21",
"maxmind": "^4.3.18",
"md5-file": "^5.0.0",
Expand All @@ -69,6 +72,7 @@
},
"devDependencies": {
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.7",
"@types/debug": "^4.1.12",
"@types/ejs": "^3.1.5",
"@types/express": "^4.17.21",
Expand Down
Loading

0 comments on commit f4c6a2f

Please sign in to comment.