Full, interactive i18n (still some quests to enable it though)

This commit is contained in:
Pieter Vander Vennet 2020-07-21 02:55:28 +02:00
parent fd6f77c98e
commit 0f2dff8f41
15 changed files with 205 additions and 90 deletions

View file

@ -50,7 +50,7 @@ export class LayerDefinition {
/**
* This UIElement is rendered as title element in the popup
*/
title: TagRenderingOptions | UIElement;
title: TagRenderingOptions;
/**
* These are the questions/shown attributes in the popup
*/

View file

@ -1,13 +1,18 @@
import {LayerDefinition} from "./LayerDefinition";
import { UIElement } from "../UI/UIElement";
import {UIElement} from "../UI/UIElement";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import Translation from "../UI/i18n/Translation";
import Translations from "../UI/i18n/Translations";
import Locale from "../UI/i18n/Locale";
import {VariableUiElement} from "../UI/Base/VariableUIElement";
import {OsmConnection, UserDetails} from "../Logic/OsmConnection";
import {UIEventSource} from "../UI/UIEventSource";
/**
* A layout is a collection of settings of the global view (thus: welcome text, title, selection of layers).
*/
export class Layout {
public name: string;
public title: UIElement;
public layers: LayerDefinition[];
@ -45,8 +50,8 @@ export class Layout {
startLat: number,
startLon: number,
welcomeMessage: UIElement | string,
gettingStartedPlzLogin: UIElement | string = "Please login to get started",
welcomeBackMessage: UIElement | string = "You are logged in. Welcome back!",
gettingStartedPlzLogin: UIElement | string = Translations.t.general.getStarted,
welcomeBackMessage: UIElement | string = Translations.t.general.welcomeBack,
welcomeTail: UIElement | string = ""
) {
this.supportedLanguages = supportedLanguages;
@ -56,11 +61,62 @@ export class Layout {
this.startzoom = startzoom;
this.name = name;
this.layers = layers;
this.welcomeMessage =Translations.W(welcomeMessage)
this.welcomeMessage = Translations.W(welcomeMessage)
this.gettingStartedPlzLogin = Translations.W(gettingStartedPlzLogin);
this.welcomeBackMessage = Translations.W(welcomeBackMessage);
this.welcomeTail = Translations.W(welcomeTail);
}
}
export class WelcomeMessage extends UIElement {
private readonly layout: Layout;
private readonly userDetails: UIEventSource<UserDetails>;
private osmConnection: OsmConnection;
private readonly description: UIElement;
private readonly plzLogIn: UIElement;
private readonly welcomeBack: UIElement;
private readonly tail: UIElement;
constructor(layout: Layout, osmConnection: OsmConnection) {
super(osmConnection.userDetails);
this.ListenTo(Locale.language);
this.osmConnection = osmConnection;
this.layout = layout;
this.userDetails = osmConnection.userDetails;
this.description = layout.welcomeMessage;
console.log(" >>>>",this.description, "DESCR ")
this.plzLogIn = layout.gettingStartedPlzLogin;
this.welcomeBack = layout.welcomeBackMessage;
this.tail = layout.welcomeTail;
}
InnerRender(): string {
return "<div id='welcomeMessage'>" +
this.description.Render() +
"<br/>"+
(this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render() +
"<br/>"+
this.tail.Render() +
"</div>"
;
/*
return new VariableUiElement(
this.userDetails.map((userdetails) => {
}),
function () {
}).ListenTo(Locale.language);*/
}
protected InnerUpdate(htmlElement: HTMLElement) {
this.osmConnection.registerActivateOsmAUthenticationClass()
}
}

View file

@ -19,13 +19,14 @@ export default class Cyclofix extends Layout {
16,
50.8465573,
4.3516970,
/* Translations.t.cyclofix.title/*/
new Combine([
"<h3>",
Translations.t.cyclofix.title,
"</h3><br/><p>",
Translations.t.cyclofix.description,
"</p>"
]),
"", "");
])//*/
);
}
}

View file

@ -1,38 +1,61 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
import Combine from "../../../UI/Base/Combine";
class ParkingTypeHelper {
static GenerateMappings() {
const images = {
stands: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg/100px-Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg",
wall_loops: "https://wiki.openstreetmap.org/w/images/thumb/c/c2/Bike-parking-wheelbender.jpg/100px-Bike-parking-wheelbender.jpg",
handlebar_holder: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Bicycle_parking_handlebar_holder.jpg/100px-Bicycle_parking_handlebar_holder.jpg",
shed: "https://wiki.openstreetmap.org/w/images/thumb/b/b2/Bike-shelter.jpg/100px-Bike-shelter.jpg",
rack: "https://wiki.openstreetmap.org/w/images/thumb/4/41/Triton_Bike_Rack.png/100px-Triton_Bike_Rack.png",
"two-tier": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG/100px-Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG"
};
const toImg = (url) => `<img src=${url}>`
const mappings = [];
const to = Translations.t.cyclofix.parking.type
for (const imagesKey in images) {
const mapping =
{
k: new Tag("bicycle_parking", imagesKey),
txt: new Combine([
to[imagesKey],
to.eg,
toImg(images[imagesKey])
])
};
mappings.push(mapping);
}
return mappings;
}
}
export default class ParkingType extends TagRenderingOptions {
constructor() {
const images = {
stands: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg/100px-Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg",
loops: "https://wiki.openstreetmap.org/w/images/thumb/c/c2/Bike-parking-wheelbender.jpg/100px-Bike-parking-wheelbender.jpg",
handlebar: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Bicycle_parking_handlebar_holder.jpg/100px-Bicycle_parking_handlebar_holder.jpg",
shed: "https://wiki.openstreetmap.org/w/images/thumb/b/b2/Bike-shelter.jpg/100px-Bike-shelter.jpg",
rack: "https://wiki.openstreetmap.org/w/images/thumb/4/41/Triton_Bike_Rack.png/100px-Triton_Bike_Rack.png",
double: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG/100px-Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG"
}
const toImg = (url) => `<img src=${url}>`
const to = Translations.t.cyclofix.parking.type
super({
priority: 5,
question: to.question.Render(),
question: to.question,
freeform: {
key: "bicycle_parking",
extraTags: new Tag("fixme", "Freeform bicycle_parking= tag used: possibly a wrong value"),
template: to.template.txt,
renderTemplate: to.render.txt,
placeholder: Translations.t.cyclofix.freeFormPlaceholder.txt,
placeholder: Translations.t.cyclofix.freeFormPlaceholder,
},
mappings: [
{k: new Tag("bicycle_parking", "stands"), txt: `${to.stands.Render()}, bijvoorbeeld: ${toImg(images.stands)}`},
{k: new Tag("bicycle_parking", "wall_loops"), txt: `${to.loops.Render()}, bijvoorbeeld: ${toImg(images.loops)}`},
{k: new Tag("bicycle_parking", "handlebar_holder"), txt: `${to.handlebar.Render()}, bijvoorbeeld: ${toImg(images.handlebar)}`},
{k: new Tag("bicycle_parking", "shed"), txt: `${to.shed.Render()}, bijvoorbeeld: ${toImg(images.shed)}`},
{k: new Tag("bicycle_parking", "rack"), txt: `${to.rack.Render()}, bijvoorbeeld: ${toImg(images.rack)}`},
{k: new Tag("bicycle_parking", "two-tier"), txt: `${to.double.Render()}, bijvoorbeeld: ${toImg(images.double)}`}
]
mappings: ParkingTypeHelper.GenerateMappings()
});
}
}

View file

@ -7,11 +7,11 @@ export default class PumpManometer extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.manometer
super({
question: to.question.Render(),
question: to.question,
mappings: [
{k: new Tag("manometer", "yes"), txt: to.yes.Render()},
{k: new Tag("manometer", "no"), txt: to.no.Render()},
{k: new Tag("manometer", "broken"), txt: to.broken.Render()}
{k: new Tag("manometer", "yes"), txt: to.yes},
{k: new Tag("manometer", "no"), txt: to.no},
{k: new Tag("manometer", "broken"), txt: to.broken}
]
});
}

View file

@ -14,6 +14,7 @@ import {InputElementWrapper} from "../UI/Input/InputElementWrapper";
import {FixedInputElement} from "../UI/Input/FixedInputElement";
import {RadioButton} from "../UI/Input/RadioButton";
import Translations from "../UI/i18n/Translations";
import Locale from "../UI/i18n/Locale";
export class TagRenderingOptions implements TagDependantUIElementConstructor {
@ -29,7 +30,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
tagsPreprocessor?: (tags: any) => any;
template: string;
renderTemplate: string;
placeholder?: string;
placeholder?: string | UIElement;
extraTags?: TagsFilter
};
mappings?: { k: TagsFilter; txt: string | UIElement; priority?: number, substitute?: boolean }[]
@ -78,7 +79,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
@ -148,7 +149,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
key: string, template: string,
renderTemplate: string,
placeholder?: string,
placeholder?: string | UIElement,
extraTags?: TagsFilter
};
@ -172,13 +173,14 @@ class TagRendering extends UIElement implements TagDependantUIElement {
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
tagsPreprocessor?: ((tags: any) => any),
mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]
}) {
super(tags);
this.ListenTo(Locale.language);
const self = this;
this.ListenTo(this._questionSkipped);
this.ListenTo(this._editMode);
@ -264,13 +266,13 @@ class TagRendering extends UIElement implements TagDependantUIElement {
const cancelContents = this._editMode.map((isEditing) => {
if (isEditing) {
return "<span class='skip-button'>Cancel</span>";
return "<span class='skip-button'>"+Translations.t.general.cancel.R()+"</span>";
} else {
return "<span class='skip-button'>Skip this question</span>";
return "<span class='skip-button'>"+Translations.t.general.skip.R()+"</span>";
}
});
// And at last, set up the skip button
this._skipButton = new VariableUiElement(cancelContents).onClick(cancel);
this._skipButton = new VariableUiElement(cancelContents).onClick(cancel) ;
}
@ -278,7 +280,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]

View file

@ -2,18 +2,30 @@ import {UIElement} from "../UIElement";
import Translations from "../i18n/Translations";
export default class Combine extends UIElement {
private uiElements: UIElement[];
private uiElements: (string | UIElement)[];
constructor(uiElements: (string | UIElement)[]) {
super(undefined);
this.uiElements = uiElements.map(Translations.W);
this.uiElements = uiElements;
}
InnerRender(): string {
let elements = "";
for (const element of this.uiElements) {
elements += element.Render();
if (element instanceof UIElement) {
elements += element.Render();
} else {
elements += element;
}
}
return elements;
}
protected InnerUpdate(htmlElement: HTMLElement) {
for (const element of this.uiElements) {
if (element instanceof UIElement) {
element.Update();
}
}
}
}

View file

@ -1,11 +1,13 @@
/**
* Keeps 'messagebox' and 'messageboxmobile' in sync, shows a 'close' button on the latter one
*/
import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
export class MessageBoxHandler {
/**
* Handles the full screen popup on mobile
*/
export class FullScreenMessageBoxHandler {
private _uielement: UIEventSource<UIElement>;
constructor(uielement: UIEventSource<UIElement>,
@ -22,13 +24,13 @@ export class MessageBoxHandler {
}
}
new VariableUiElement(new UIEventSource<string>("<h2>Return to the map</h2>"))
Translations.t.general.returnToTheMap
.onClick(() => {
console.log("Clicked 'return to the map'")
uielement.setData(undefined);
onClear();
})
.AttachTo("to-the-map");
.AttachTo("to-the-map-h2");
}

View file

@ -1,5 +1,6 @@
import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import Translations from "./i18n/Translations";
export class SaveButton extends UIElement {
private _value: UIEventSource<any>;
@ -17,9 +18,9 @@ export class SaveButton extends UIElement {
this._value.data === null
|| this._value.data === ""
) {
return "<span class='save-non-active'>Opslaan</span>"
return "<span class='save-non-active'>"+Translations.t.general.save.Render()+"</span>"
}
return "<span class='save'>Save</span>";
return "<span class='save'>"+Translations.t.general.save.Render()+"</span>";
}
}

View file

@ -13,10 +13,6 @@ export abstract class UIElement {
this.id = "ui-element-" + UIElement.nextId;
this._source = source;
UIElement.nextId++;
if (UIElement.nextId % 100 == 0) {
console.log(UIElement.nextId)
}
this.ListenTo(source);
}
@ -97,8 +93,7 @@ export abstract class UIElement {
AttachTo(divId: string) {
let element = document.getElementById(divId);
if (element === null) {
console.log("SEVERE: could not attach UIElement to ", divId);
return;
throw "SEVERE: could not attach UIElement to " + divId;
}
element.innerHTML = this.Render();
this.Update();

View file

@ -30,6 +30,4 @@ export default class Translation extends UIElement {
return new Translation(this.translations).Render();
}
}

View file

@ -36,13 +36,15 @@ export default class Translations {
nl: 'Van welk type is deze fietsenparking?',
fr: 'TODO: fr'
}),
eg: new T({en: ", for example", nl: ", bijvoorbeeld"}),
stands: new T({en: 'Staple racks', nl: 'Nietjes', fr: 'TODO: fr'}),
loops: new T({en: 'Wheel rack/loops', nl: 'Wielrek/lussen', fr: 'TODO: fr'}),
handlebar: new T({en: 'Handlebar holder', nl: 'Stuurhouder', fr: 'TODO: fr'}),
wall_loops: new T({en: 'Wheel rack/loops', nl: 'Wielrek/lussen', fr: 'TODO: fr'}),
handlebar_holder: new T({en: 'Handlebar holder', nl: 'Stuurhouder', fr: 'TODO: fr'}),
shed: new T({en: 'Shed', nl: 'Schuur', fr: 'TODO: fr'}),
rack: new T({en: 'Rack', nl: 'Rek', fr: 'TODO: fr'}),
double: new T({en: 'Two-tiered', nl: 'Dubbel (twee verdiepingen)', fr: 'TODO: fr'}),
"two-tier": new T({en: 'Two-tiered', nl: 'Dubbel (twee verdiepingen)', fr: 'TODO: fr'}),
},
operator: {
render: new T({
en: 'This bike parking is operated by {operator}',
@ -293,9 +295,15 @@ export default class Translations {
ready: new T({en: 'Done!', nl: 'Klaar!', fr: 'TODO: fr'}),
},
general: {
loginWithOpenStreetMap: new T({en: "Login with OpenStreetMap", nl: "Aanmelden met OpenStreetMap"})
,
loginWithOpenStreetMap: new T({en: "Login with OpenStreetMap", nl: "Aanmelden met OpenStreetMap"}),
getStarted: new T({
en: "<span class='activate-osm-authentication'>Login with OpenStreetMap</span> or <a href='https://www.openstreetmap.org/user/new' target='_blank'>make a free account to get started</a>",
nl: "<span class='activate-osm-authentication'>Meld je aan met je OpenStreetMap-account</span> of <a href='https://www.openstreetmap.org/user/new' target='_blank'>maak snel en gratis een account om te beginnen/a>",
}),
welcomeBack: new T({
en: "You are logged in, welcome back!",
nl: "Je bent aangemeld. Welkom terug!"
}),
search: {
search: new Translation({
en: "Search a location",
@ -314,7 +322,23 @@ export default class Translations {
nl: "Niet gelukt..."
})
}
},
returnToTheMap: new T({
en: "Return to the map",
nl: "Naar de kaart"
}),
save: new T({
en: "Save",
nl: "Opslaan"
}),
cancel: new T({
en: "Cancel",
nl: "Annuleren"
}),
skip: new T({
en: "Skip this question",
nl: "Vraag overslaan"
})
}
}

View file

@ -17,9 +17,14 @@
<body>
<div id="messagesboxmobilewrapper">
<div id="messagesboxmobile-scroll">
<div id="messagesboxmobile"> </div>
<div id="messagesboxmobile"></div>
</div>
<div id="to-the-map">
<h2 id="to-the-map-h2">
Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is
blocking it.
</h2>
</div>
<div id="to-the-map">Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is blocking it.</div>
</div>
<div id="topleft-tools">

View file

@ -11,7 +11,7 @@ import {Tag, TagUtils} from "./Logic/TagsFilter";
import {FilteredLayer} from "./Logic/FilteredLayer";
import {LayerUpdater} from "./Logic/LayerUpdater";
import {UIElement} from "./UI/UIElement";
import {MessageBoxHandler} from "./UI/MessageBoxHandler";
import {FullScreenMessageBoxHandler} from "./UI/FullScreenMessageBoxHandler";
import {Overpass} from "./Logic/Overpass";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {GeoLocationHandler} from "./Logic/GeoLocationHandler";
@ -25,7 +25,7 @@ import {All} from "./Customizations/Layouts/All";
import Translations from "./UI/i18n/Translations";
import Translation from "./UI/i18n/Translation";
import Locale from "./UI/i18n/Locale";
import {Layout} from "./Customizations/Layout";
import {Layout, WelcomeMessage} from "./Customizations/Layout";
import {DropDown} from "./UI/Input/DropDown";
import {FixedInputElement} from "./UI/Input/FixedInputElement";
import {FixedUiElement} from "./UI/Base/FixedUiElement";
@ -135,6 +135,7 @@ const osmConnection = new OsmConnection(dryRun);
Locale.language.syncWith(osmConnection.GetPreference("language"));
// @ts-ignore
window.setLanguage = function (language: string) {
Locale.language.setData(language)
}
@ -265,29 +266,18 @@ new SearchAndGo(bm).AttachTo("searchbox");
new CollapseButton("messagesbox")
.AttachTo("collapseButton");
var generateWelcomeMessage = () => {
return new VariableUiElement(
osmConnection.userDetails.map((userdetails) => {
var login = layoutToUse.gettingStartedPlzLogin.Render();
if (userdetails.loggedIn) {
login = layoutToUse.welcomeBackMessage.Render();
}
return "<div id='welcomeMessage'>" +
layoutToUse.welcomeMessage.Render() + login + layoutToUse.welcomeTail.Render() +
"</div>";
}),
function () {
osmConnection.registerActivateOsmAUthenticationClass()
}).ListenTo(Locale.language);
}
generateWelcomeMessage().AttachTo("messagesbox");
fullScreenMessage.setData(generateWelcomeMessage());
new WelcomeMessage(layoutToUse, osmConnection).AttachTo("messagesbox");
fullScreenMessage.setData(
new WelcomeMessage(layoutToUse, osmConnection)
);
var messageBox = new MessageBoxHandler(fullScreenMessage, () => {
new FullScreenMessageBoxHandler(fullScreenMessage, () => {
selectedElement.setData(undefined)
});
}).update();
// fullScreenMessage.setData(generateWelcomeMessage());
new CenterMessageBox(
minZoom,
@ -310,4 +300,6 @@ new GeoLocationHandler(bm).AttachTo("geolocate-button");
// --------------- Send a ping to start various action --------
locationControl.ping();
messageBox.update();
window.setTimeout(() => {Locale.language.setData("nl")}, 5000)

View file

@ -1,9 +1,13 @@
import {DropDown} from "./UI/Input/DropDown";
import Locale from "./UI/i18n/Locale";
import Combine from "./UI/Base/Combine";
import Translations from "./UI/i18n/Translations";
console.log("Hello world")
let languagePicker = new DropDown("", ["en", "nl"].map(lang => {
return {value: lang, shown: lang}
}
), Locale.language).AttachTo("maindiv");
), Locale.language).AttachTo("maindiv");
new Combine(["abc",Translations.t.cyclofix.title, Translations.t.cyclofix.title]).AttachTo("extradiv");