From f7844d8b2bae84f29680b2caace9c9962681038c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Apr 2022 01:32:04 +0200 Subject: [PATCH 1/7] Add translations to more parts of the import helper GUI --- .../CompareToAlreadyExistingNotes.ts | 65 ++++++++++--------- UI/ImportFlow/FlowStep.ts | 8 +-- UI/ImportFlow/ImportHelperGui.ts | 18 ++--- UI/ImportFlow/Introdution.ts | 6 +- UI/ImportFlow/LoginToImport.ts | 2 +- UI/ImportFlow/PreviewPanel.ts | 6 +- UI/ImportFlow/SelectTheme.ts | 37 +++++------ langs/en.json | 55 ++++++++++++---- 8 files changed, 115 insertions(+), 82 deletions(-) diff --git a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts index 868504cde..eb0d40f71 100644 --- a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts +++ b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts @@ -21,6 +21,7 @@ import {FixedUiElement} from "../Base/FixedUiElement"; import {VariableUiElement} from "../Base/VariableUIElement"; import * as known_layers from "../../assets/generated/known_layers.json" import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson"; +import Translations from "../i18n/Translations"; /** * Filters out points for which the import-note already exists, to prevent duplicates @@ -28,11 +29,11 @@ import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson"; export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> { public IsValid: UIEventSource - public Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[] , theme: string}> + public Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> - constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) { - + constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) { + const t = Translations.t.importHelper.compareToAlreadyExistingNotes const layerConfig = known_layers.layers.filter(l => l.id === params.layer.id)[0] if (layerConfig === undefined) { console.error("WEIRD: layer not found in the builtin layer overview") @@ -45,7 +46,7 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ layerDef: importLayer } const allNotesWithinBbox = new GeoJsonSource(flayer, params.bbox.padAbsolute(0.0001)) - + allNotesWithinBbox.features.map(f => MetaTagging.addMetatags( f, { @@ -63,7 +64,6 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ ) ) const alreadyOpenImportNotes = new FilteringFeatureSource(state, undefined, allNotesWithinBbox) - alreadyOpenImportNotes.features.addCallbackD(features => console.log("Loaded and filtered features are", features)) const map = Minimap.createMiniMap() map.SetClass("w-full").SetStyle("height: 500px") @@ -99,43 +99,46 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ }) super([ - new Title("Compare with already existing 'to-import'-notes"), + new Title(t.titleLong), new VariableUiElement( alreadyOpenImportNotes.features.map(notesWithImport => { - if(allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined){ - return new FixedUiElement("Loading notes failed: "+allNotesWithinBbox.state.data["error"] ) + if (allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined) { + t.loadingFailed.Subs(allNotesWithinBbox.state.data) } if (allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0) { - return new Loading("Fetching notes from OSM") + return new Loading(t.loading) } if (notesWithImport.length === 0) { - return new FixedUiElement("No previous import notes found").SetClass("thanks") + return t.noPreviousNotesFound.SetClass("thanks") } return new Combine([ - "The red elements on the next map are all the data points from your dataset. There are "+params.features.length+" elements in your dataset.", + t.mapExplanation.Subs(params.features), map, - - new VariableUiElement( partitionedImportPoints.map(({noNearby, hasNearby}) => { - - if(noNearby.length === 0){ - // Nothing can be imported - return new FixedUiElement("All of the proposed points have (or had) an import note already").SetClass("alert w-full block").SetStyle("padding: 0.5rem") - } - - if(hasNearby.length === 0){ - // All points can be imported - return new FixedUiElement("All of the proposed points have don't have a previous import note nearby").SetClass("thanks w-full block").SetStyle("padding: 0.5rem") - } - - return new Combine([ - new FixedUiElement(hasNearby.length+" points do have an already existing import note within "+maxDistance.data+" meter.").SetClass("alert"), - "These data points will not be imported and are shown as red dots on the map below", - comparisonMap.SetClass("w-full") - ]).SetClass("w-full") + new VariableUiElement(partitionedImportPoints.map(({noNearby, hasNearby}) => { + + if (noNearby.length === 0) { + // Nothing can be imported + return t.completelyImported.SetClass("alert w-full block").SetStyle("padding: 0.5rem") + } + + if (hasNearby.length === 0) { + // All points can be imported + return t.nothingNearby.SetClass("thanks w-full block").SetStyle("padding: 0.5rem") + + } + + return new Combine([ + t.someNearby.Subs({ + hasNearby: hasNearby.length, + distance: maxDistance.data + }).SetClass("alert"), + t.wontBeImported, + comparisonMap.SetClass("w-full") + ]).SetClass("w-full") })) - - + + ]).SetClass("flex flex-col") }, [allNotesWithinBbox.features, allNotesWithinBbox.state]) diff --git a/UI/ImportFlow/FlowStep.ts b/UI/ImportFlow/FlowStep.ts index e0fde7b60..44ad03d3b 100644 --- a/UI/ImportFlow/FlowStep.ts +++ b/UI/ImportFlow/FlowStep.ts @@ -25,15 +25,15 @@ export class FlowPanelFactory { this._stepNames = stepNames; } - public static start(name: string | BaseUIElement, step: FlowStep): FlowPanelFactory { - return new FlowPanelFactory(step, [], [name]) + public static start(name:{title: BaseUIElement}, step: FlowStep): FlowPanelFactory { + return new FlowPanelFactory(step, [], [name.title]) } - public then(name: string | BaseUIElement, construct: ((t: T) => FlowStep)): FlowPanelFactory { + public then(name: string | {title: BaseUIElement}, construct: ((t: T) => FlowStep)): FlowPanelFactory { return new FlowPanelFactory( this._initial, this._steps.concat([construct]), - this._stepNames.concat([name]) + this._stepNames.concat([name["title"] ?? name]) ) } diff --git a/UI/ImportFlow/ImportHelperGui.ts b/UI/ImportFlow/ImportHelperGui.ts index 8fb1c041b..56fb3815d 100644 --- a/UI/ImportFlow/ImportHelperGui.ts +++ b/UI/ImportFlow/ImportHelperGui.ts @@ -7,7 +7,7 @@ import MinimapImplementation from "../Base/MinimapImplementation"; import Translations from "../i18n/Translations"; import {FlowPanelFactory} from "./FlowStep"; import {RequestFile} from "./RequestFile"; -import {PreviewPanel} from "./PreviewPanel"; +import {PreviewAttributesPanel} from "./PreviewPanel"; import ConflationChecker from "./ConflationChecker"; import {AskMetadata} from "./AskMetadata"; import {ConfirmProcess} from "./ConfirmProcess"; @@ -26,16 +26,16 @@ import SelectTheme from "./SelectTheme"; export default class ImportHelperGui extends LeftIndex { constructor() { const state = new UserRelatedState(undefined) - + const t = Translations.t.importHelper; const {flow, furthestStep, titles} = FlowPanelFactory - .start("Introduction", new Introdution()) - .then("Login", _ => new LoginToImport(state)) - .then("Select file", _ => new RequestFile()) - .then("Inspect attributes", geojson => new PreviewPanel(state, geojson)) - .then("Inspect data", geojson => new MapPreview(state, geojson)) - .then("Select theme", v => new SelectTheme(v)) - .then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v)) + .start(t.introduction, new Introdution()) + .then(t.login, _ => new LoginToImport(state)) + .then(t.selectFile, _ => new RequestFile()) + .then(t.previewAttributes, geojson => new PreviewAttributesPanel(state, geojson)) + .then(t.mapPreview, geojson => new MapPreview(state, geojson)) + .then(t.selectTheme, v => new SelectTheme(v)) + .then(t.compareToAlreadyExistingNotes, v => new CompareToAlreadyExistingNotes(state, v)) .then("Compare with existing data", v => new ConflationChecker(state, v)) .then("License and community check", v => new ConfirmProcess(v)) .then("Metadata", (v) => new AskMetadata(v)) diff --git a/UI/ImportFlow/Introdution.ts b/UI/ImportFlow/Introdution.ts index 859fd8e4b..5cd65721b 100644 --- a/UI/ImportFlow/Introdution.ts +++ b/UI/ImportFlow/Introdution.ts @@ -10,9 +10,9 @@ export default class Introdution extends Combine implements FlowStep { constructor() { super([ - new Title(Translations.t.importHelper.title), - Translations.t.importHelper.description, - Translations.t.importHelper.importFormat, + new Title(Translations.t.importHelper.introduction.title), + Translations.t.importHelper.introduction.description, + Translations.t.importHelper.introduction.importFormat, ]); this.SetClass("flex flex-col") } diff --git a/UI/ImportFlow/LoginToImport.ts b/UI/ImportFlow/LoginToImport.ts index 3c5df6e67..8a73042ce 100644 --- a/UI/ImportFlow/LoginToImport.ts +++ b/UI/ImportFlow/LoginToImport.ts @@ -21,7 +21,7 @@ export default class LoginToImport extends Combine implements FlowStep t.loginIsCorrect.Subs(ud)))]) const isValid = state.osmConnection.userDetails.map(ud => LoginToImport.whitelist.indexOf(ud.uid) >= 0 || ud.csCount >= Constants.userJourney.importHelperUnlock) diff --git a/UI/ImportFlow/PreviewPanel.ts b/UI/ImportFlow/PreviewPanel.ts index fab9440c0..8d5495004 100644 --- a/UI/ImportFlow/PreviewPanel.ts +++ b/UI/ImportFlow/PreviewPanel.ts @@ -12,16 +12,16 @@ import List from "../Base/List"; import CheckBoxes from "../Input/Checkboxes"; /** - * Shows the data to import on a map, asks for the correct layer to be selected + * Shows the attributes by value, requests to check them of */ -export class PreviewPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> { +export class PreviewAttributesPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> { public readonly IsValid: UIEventSource; public readonly Value: UIEventSource<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> constructor( state: UserRelatedState, geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) { - const t = Translations.t.importHelper; + const t = Translations.t.importHelper.previewAttributes; const propertyKeys = new Set() for (const f of geojson.features) { diff --git a/UI/ImportFlow/SelectTheme.ts b/UI/ImportFlow/SelectTheme.ts index 1a84aee0f..99339ab62 100644 --- a/UI/ImportFlow/SelectTheme.ts +++ b/UI/ImportFlow/SelectTheme.ts @@ -10,12 +10,12 @@ import Title from "../Base/Title"; import {RadioButton} from "../Input/RadioButton"; import {And} from "../../Logic/Tags/And"; import {VariableUiElement} from "../Base/VariableUIElement"; -import {FixedUiElement} from "../Base/FixedUiElement"; import Toggleable from "../Base/Toggleable"; import {BBox} from "../../Logic/BBox"; import BaseUIElement from "../BaseUIElement"; import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; import List from "../Base/List"; +import Translations from "../i18n/Translations"; export default class SelectTheme extends Combine implements FlowStep<{ features: any[], @@ -33,7 +33,7 @@ export default class SelectTheme extends Combine implements FlowStep<{ public readonly IsValid: UIEventSource; constructor(params: ({ features: any[], layer: LayerConfig, bbox: BBox, })) { - + const t = Translations.t.importHelper.selectTheme let options: InputElement[] = AllKnownLayouts.layoutsList .filter(th => th.layers.some(l => l.id === params.layer.id)) .filter(th => th.id !== "personal") @@ -69,15 +69,15 @@ export default class SelectTheme extends Combine implements FlowStep<{ }) super([ - new Title("Select a theme"), - "All of the following themes will show the import notes. However, the note on OpenStreetMap can link to only one single theme. Choose which theme that the created notes will link to", + new Title(t.title), + t.intro, themeRadios, new VariableUiElement(applicablePresets.map(applicablePresets => { if (themeRadios.GetValue().data === undefined) { return undefined } if (applicablePresets === undefined || applicablePresets.length === 0) { - return new FixedUiElement("This theme has no presets loaded. As a result, imports won't work here").SetClass("alert") + return t.noMatchingPresets.SetClass("alert") } }, [themeRadios.GetValue()])), @@ -115,11 +115,14 @@ export default class SelectTheme extends Combine implements FlowStep<{ if (unmatched === undefined || unmatched.length === 0) { return } - - const applicablePresetsOverview = applicablePresets.map(preset => new Combine([ - preset.title.txt, "needs tags", - new FixedUiElement(preset.tags.map(t => t.asHumanString()).join(" & ")).SetClass("thanks") - ])) + const t = Translations.t.importHelper.selectTheme + + const applicablePresetsOverview = applicablePresets.map(preset => + t.needsTags.Subs( + {title: preset.title, + tags:preset.tags.map(t => t.asHumanString()).join(" & ") }) + .SetClass("thanks") + ); const unmatchedPanels: BaseUIElement[] = [] for (const feat of unmatched) { @@ -133,20 +136,16 @@ export default class SelectTheme extends Combine implements FlowStep<{ const missing = [] for (const {k, v} of tags) { if (preset[k] === undefined) { - missing.push( - `Expected ${k}=${v}, but it is completely missing` - ) + missing.push(t.missing.Subs({k,v})) } else if (feat.properties[k] !== v) { - missing.push( - `Property with key ${k} does not have expected value ${v}; instead it is ${feat.properties}` - ) + missing.push(t.misMatch.Subs({k, v, properties: feat.properties})) } } if (missing.length > 0) { parts.push( new Combine([ - new FixedUiElement(`Preset ${preset.title.txt} is not applicable:`), + t.notApplicable.Subs(preset), new List(missing) ]).SetClass("flex flex-col alert") ) @@ -158,9 +157,9 @@ export default class SelectTheme extends Combine implements FlowStep<{ } return new Combine([ - new FixedUiElement(unmatched.length + " objects dont match any presets").SetClass("alert"), + t.displayNonMatchingCount.Subs(unmatched).SetClass("alert"), ...applicablePresetsOverview, - new Toggleable(new Title("The following elements don't match any of the presets"), + new Toggleable(new Title(t.unmatchedTitle), new Combine(unmatchedPanels)) ]).SetClass("flex flex-col") diff --git a/langs/en.json b/langs/en.json index e4d501476..b0d3ea464 100644 --- a/langs/en.json +++ b/langs/en.json @@ -276,17 +276,33 @@ "willBePublished": "Your picture will be published " }, "importHelper": { - "allAttributesSame": "All features to import have this tag", - "description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.", - "importFormat": "A text in a note should have the following format in order to be picked up:
[A bit of introduction]
https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import
[all tags of the feature]
", - "inspectDataTitle": "Inspect data of {count} features to import", + "compareToAlreadyExistingNotes": { + "completelyImported": "All of the proposed points have (or had) an import note already", + "loading": "Fetching notes from OSM", + "loadingFailed": "Loading notes failed due to {error}", + "mapExplanation": "The red elements on the next map are all the data points from your dataset. There are {length} elements in your dataset.", + "noPreviousNotesFound": "No previous import notes found", + "nothingNearby": "All of the proposed points have don't have a previous import note nearby", + "someNearby": "{hasNearby} points do have an already existing import note within {distance} meter", + "title": "Compare with existing notes", + "titleLong": "Compare with already existing 'to-import'-notes", + "wontBeImported": "These data points will not be imported and are shown as red dots on the map below" + }, "inspectDidAutoDected": "Layer was chosen automatically", - "inspectLooksCorrect": "These values look correct", - "lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.", + "introduction": { + "description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.", + "importFormat": "A text in a note should have the following format in order to be picked up:
[A bit of introduction]
https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import
[all tags of the feature]
", + "title": "Introduction" + }, "locked": "You need at least {importHelperUnlock} to use the import helper", - "loggedInWith": "You are currently logged in as {name} and have made {csCount} changesets", - "loginIsCorrect": "{name} is the correct account to create the import notes with.", - "loginRequired": "You have to be logged in to continue", + "login": { + "lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.", + "loggedInWith": "You are currently logged in as {name} and have made {csCount} changesets", + "loginIsCorrect": "{name} is the correct account to create the import notes with.", + "loginRequired": "You have to be logged in to continue", + "title": "Login", + "userAccountTitle": "Select user account" + }, "mapPreview": { "autodetected": "The layer was automatically deducted based on the properties", "confirm": "The features are on the right location on the map", @@ -294,6 +310,13 @@ "selectLayer": "Which layer does this import match with?", "title": "Map preview" }, + "previewAttributes": { + "allAttributesSame": "All features to import have this tag", + "inspectDataTitle": "Inspect data of {count} features to import", + "inspectLooksCorrect": "These values look correct", + "someHaveSame": "{count} features to import have this tag, this is {percentage}% of the total", + "title": "Inspect attributes" + }, "selectFile": { "description": "Select a .csv or .geojson file to get started", "errDuplicate": "Some columns have the same name", @@ -308,10 +331,18 @@ "noFilesLoaded": "No file is currently loaded", "title": "Select file" }, - "selectLayer": "Select a layer...", - "someHaveSame": "{count} features to import have this tag, this is {percentage}% of the total", + "selectTheme": { + "displayNonMatchingCount": "{length} objects dont match any presets", + "intro": "All of the following themes will show the import notes. However, the note on OpenStreetMap can link to only one single theme. Choose which theme that the created notes will link to", + "misMatch": "Property with key {k} does not have expected value {v}; instead it is {properties}", + "missing": "Expected $k}={v}, but it is completely missing", + "needsTags": "{title} needs tags {tags}", + "noMatchingPresets": "This theme has no presets loaded. As a result, imports won't work here", + "notApplicable": "Preset {title} is not applicable:", + "title": "Select a theme", + "unmatchedTitle": "The following elements don't match any of the presets" + }, "title": "Import helper", - "userAccountTitle": "Select user account", "validateDataTitle": "Validate data" }, "importInspector": { From 8e2e227563746102c95bd6ce42c0ccb14979c8bc Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Apr 2022 03:01:54 +0200 Subject: [PATCH 2/7] Polishing and translations for the import helper --- UI/ImportFlow/AskMetadata.ts | 39 +++++++++----- UI/ImportFlow/ConfirmProcess.ts | 37 ++++---------- UI/ImportFlow/CreateNotes.ts | 87 +++++++++++++++++++------------- UI/ImportFlow/FlowStep.ts | 4 +- UI/ImportFlow/ImportHelperGui.ts | 10 ++-- UI/ImportFlow/Introdution.ts | 28 +++++++++- langs/en.json | 43 ++++++++++++++-- 7 files changed, 161 insertions(+), 87 deletions(-) diff --git a/UI/ImportFlow/AskMetadata.ts b/UI/ImportFlow/AskMetadata.ts index 9f7c16caf..125b43ed3 100644 --- a/UI/ImportFlow/AskMetadata.ts +++ b/UI/ImportFlow/AskMetadata.ts @@ -4,8 +4,12 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import ValidatedTextField from "../Input/ValidatedTextField"; import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource"; import Title from "../Base/Title"; -import {FixedUiElement} from "../Base/FixedUiElement"; import {VariableUiElement} from "../Base/VariableUIElement"; +import Translations from "../i18n/Translations"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import {SubtleButton} from "../Base/SubtleButton"; +import Svg from "../../Svg"; +import {Utils} from "../../Utils"; export class AskMetadata extends Combine implements FlowStep<{ features: any[], @@ -25,7 +29,7 @@ export class AskMetadata extends Combine implements FlowStep<{ public readonly IsValid: UIEventSource; constructor(params: ({ features: any[], theme: string })) { - + const t = Translations.t.importHelper.askMetadata const introduction = ValidatedTextField.ForType("text").ConstructInputElement({ value: LocalStorageSource.Get("import-helper-introduction-text"), inputStyle: "width: 100%" @@ -42,28 +46,39 @@ export class AskMetadata extends Combine implements FlowStep<{ }) super([ - new Title("Set metadata"), - "Before adding " + params.features.length + " notes, please provide some extra information.", - "Please, write an introduction for someone who sees the note", + new Title(t.title), + t.intro.Subs({count: params.features.length}), + t.giveDescription, introduction.SetClass("w-full border border-black"), - "What is the source of this data? If 'source' is set in the feature, this value will be ignored", - source.SetClass("w-full border border-black"), - "On what wikipage can one find more information about this import?", + t.giveSource, + source.SetClass("w-full border border-black"), + t.giveWikilink , wikilink.SetClass("w-full border border-black"), new VariableUiElement(wikilink.GetValue().map(wikilink => { try{ const url = new URL(wikilink) if(url.hostname.toLowerCase() !== "wiki.openstreetmap.org"){ - return new FixedUiElement("Expected a link to wiki.openstreetmap.org").SetClass("alert"); + return t.shouldBeOsmWikilink.SetClass("alert"); } if(url.pathname.toLowerCase() === "/wiki/main_page"){ - return new FixedUiElement("Nope, the home page isn't allowed either. Enter the URL of a proper wikipage documenting your import").SetClass("alert"); + return t.shouldNotBeHomepage.SetClass("alert"); } }catch(e){ - return new FixedUiElement("Not a valid URL").SetClass("alert") + return t.shouldBeUrl.SetClass("alert") } - })) + })), + t.orDownload, + new SubtleButton(Svg.download_svg(), t.downloadGeojson).OnClickWithLoading("Preparing your download", + async ( ) => { + const geojson = { + type:"FeatureCollection", + features: params.features + } + Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "prepared_import_"+params.theme+".geojson",{ + mimetype: "application/vnd.geo+json" + }) + }) ]); this.SetClass("flex flex-col") diff --git a/UI/ImportFlow/ConfirmProcess.ts b/UI/ImportFlow/ConfirmProcess.ts index 0cdf28be7..d7168a86d 100644 --- a/UI/ImportFlow/ConfirmProcess.ts +++ b/UI/ImportFlow/ConfirmProcess.ts @@ -2,45 +2,30 @@ import Combine from "../Base/Combine"; import {FlowStep} from "./FlowStep"; import {UIEventSource} from "../../Logic/UIEventSource"; import Link from "../Base/Link"; -import {FixedUiElement} from "../Base/FixedUiElement"; import CheckBoxes from "../Input/Checkboxes"; import Title from "../Base/Title"; -import {SubtleButton} from "../Base/SubtleButton"; -import Svg from "../../Svg"; -import {Utils} from "../../Utils"; +import Translations from "../i18n/Translations"; export class ConfirmProcess extends Combine implements FlowStep<{ features: any[], theme: string }> { public IsValid: UIEventSource - public Value: UIEventSource<{ features: any[],theme: string }> - - constructor(v: { features: any[], theme: string }) { + public Value: UIEventSource<{ features: any[], theme: string }> + constructor(v: { features: any[], theme: string }) { + const t = Translations.t.importHelper.confirmProcess; const toConfirm = [ - new Combine(["I have read the ", new Link("import guidelines on the OSM wiki", "https://wiki.openstreetmap.org/wiki/Import_guidelines", true)]), - new FixedUiElement("I did contact the (local) community about this import"), - new FixedUiElement("The license of the data to import allows it to be imported into OSM. They are allowed to be redistributed commercially, with only minimal attribution"), - new FixedUiElement("The process is documented on the OSM-wiki (you'll need this link later)") + new Link(t.readImportGuidelines, "https://wiki.openstreetmap.org/wiki/Import_guidelines", true), + t.contactedCommunity, + t.licenseIsCompatible, + t.wikipageIsMade ]; - const licenseClear = new CheckBoxes(toConfirm) super([ - new Title("Did you go through the import process?"), - licenseClear, - new FixedUiElement("Alternatively, you can download the dataset to import directly"), - new SubtleButton(Svg.download_svg(), "Download geojson").OnClickWithLoading("Preparing your download", - async ( ) => { - const geojson = { - type:"FeatureCollection", - features: v.features - } - Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "prepared_import_"+v.theme+".geojson",{ - mimetype: "application/vnd.geo+json" - }) - }) + new Title(t.titleLong), + new CheckBoxes(toConfirm), ]); this.SetClass("link-underline") - this.IsValid = licenseClear.GetValue().map(selected => toConfirm.length == selected.length) + this.IsValid = new CheckBoxes(toConfirm).GetValue().map(selected => toConfirm.length == selected.length) this.Value = new UIEventSource<{ features: any[], theme: string }>(v) } } \ No newline at end of file diff --git a/UI/ImportFlow/CreateNotes.ts b/UI/ImportFlow/CreateNotes.ts index 1364d3675..8b8f1ffc4 100644 --- a/UI/ImportFlow/CreateNotes.ts +++ b/UI/ImportFlow/CreateNotes.ts @@ -8,47 +8,58 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import {FixedUiElement} from "../Base/FixedUiElement"; import {SubtleButton} from "../Base/SubtleButton"; import Svg from "../../Svg"; +import Translations from "../i18n/Translations"; export class CreateNotes extends Combine { + + public static createNoteContents(feature: {properties: any, geometry: {coordinates: [number,number]}}, + options: {wikilink: string; intro: string; source: string, theme: string } + ): string[]{ + const src = feature.properties["source"] ?? feature.properties["src"] ?? options.source + delete feature.properties["source"] + delete feature.properties["src"] + let extraNote = "" + if (feature.properties["note"]) { + extraNote = feature.properties["note"] + "\n" + delete feature.properties["note"] + } + + const tags: string [] = [] + for (const key in feature.properties) { + if (feature.properties[key] === null || feature.properties[key] === undefined) { + console.warn("Null or undefined key for ", feature.properties) + continue + } + if (feature.properties[key] === "") { + continue + } + tags.push(key + "=" + (feature.properties[key]+"").replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n")) + } + const lat = feature.geometry.coordinates[1] + const lon = feature.geometry.coordinates[0] + const note = Translations.t.importHelper.noteParts + return [ + options.intro, + extraNote, + note.datasource.Subs({source: src}).txt, + note.wikilink.Subs(options).txt, + '', + note.importEasily.txt, + `https://mapcomplete.osm.be/${options.theme}.html?z=18&lat=${lat}&lon=${lon}#import`, + ...tags] + } constructor(state: { osmConnection: OsmConnection }, v: { features: any[]; wikilink: string; intro: string; source: string, theme: string }) { - + const t = Translations.t.importHelper.createNotes; const createdNotes: UIEventSource = new UIEventSource([]) const failed = new UIEventSource([]) const currentNote = createdNotes.map(n => n.length) for (const f of v.features) { - - const src = f.properties["source"] ?? f.properties["src"] ?? v.source - delete f.properties["source"] - delete f.properties["src"] - let extraNote = "" - if (f.properties["note"]) { - extraNote = f.properties["note"] + "\n" - delete f.properties["note"] - } - - const tags: string [] = [] - for (const key in f.properties) { - if (f.properties[key] === null || f.properties[key] === undefined) { - console.warn("Null or undefined key for ", f.properties) - continue - } - if (f.properties[key] === "") { - continue - } - tags.push(key + "=" + (f.properties[key]+"").replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n")) - } + const lat = f.geometry.coordinates[1] const lon = f.geometry.coordinates[0] - const text = [v.intro, - extraNote, - "Source: " + src, - 'More information at ' + v.wikilink, - '', - 'Import this point easily with', - `https://mapcomplete.osm.be/${v.theme}.html?z=18&lat=${lat}&lon=${lon}#import`, - ...tags].join("\n") + const text = CreateNotes.createNoteContents(f, v).join("\n") state.osmConnection.openNote( lat, lon, text) @@ -62,13 +73,19 @@ export class CreateNotes extends Combine { } super([ - new Title("Creating notes"), - "Hang on while we are importing...", + new Title(t.title), + t.loading , new Toggle( - new Loading(new VariableUiElement(currentNote.map(count => new FixedUiElement("Imported " + count + " out of " + v.features.length + " notes")))), + new Loading(new VariableUiElement(currentNote.map(count => t.creating.Subs({ + count, total: v.features.length + } + + )))), new Combine([ - new FixedUiElement("All done!").SetClass("thanks"), - new SubtleButton(Svg.note_svg(), "Inspect the progress of your notes in the 'import_viewer'", { + Svg.party_svg().SetClass("w-24"), + t.done.Subs(v.features.length).SetClass("thanks"), + new SubtleButton(Svg.note_svg(), + t.openImportViewer , { url: "import_viewer.html" }) ] diff --git a/UI/ImportFlow/FlowStep.ts b/UI/ImportFlow/FlowStep.ts index 44ad03d3b..694207bcb 100644 --- a/UI/ImportFlow/FlowStep.ts +++ b/UI/ImportFlow/FlowStep.ts @@ -120,11 +120,11 @@ export class FlowPanel extends Toggle { isError.setData(true) } }), - "Select a valid value to continue", + new SubtleButton(Svg.invalid_svg(), t.notValid), initial.IsValid ), new Toggle( - new FixedUiElement("Something went wrong...").SetClass("alert"), + t.error.SetClass("alert"), undefined, isError), ]).SetClass("flex w-full justify-end space-x-2"), diff --git a/UI/ImportFlow/ImportHelperGui.ts b/UI/ImportFlow/ImportHelperGui.ts index 56fb3815d..82cd8684f 100644 --- a/UI/ImportFlow/ImportHelperGui.ts +++ b/UI/ImportFlow/ImportHelperGui.ts @@ -37,9 +37,9 @@ export default class ImportHelperGui extends LeftIndex { .then(t.selectTheme, v => new SelectTheme(v)) .then(t.compareToAlreadyExistingNotes, v => new CompareToAlreadyExistingNotes(state, v)) .then("Compare with existing data", v => new ConflationChecker(state, v)) - .then("License and community check", v => new ConfirmProcess(v)) - .then("Metadata", (v) => new AskMetadata(v)) - .finish("Note creation", v => new CreateNotes(state, v)); + .then(t.confirmProcess, v => new ConfirmProcess(v)) + .then(t.askMetadata, (v) => new AskMetadata(v)) + .finish(t.createNotes.title, v => new CreateNotes(state, v)); const toc = new List( titles.map((title, i) => new VariableUiElement(furthestStep.map(currentStep => { @@ -58,11 +58,11 @@ export default class ImportHelperGui extends LeftIndex { , true) const leftContents: BaseUIElement[] = [ - new SubtleButton(undefined, "Inspect your preview imports", { + new SubtleButton(undefined, t.gotoImportViewer, { url: "import_viewer.html" }), toc, - new Toggle(new FixedUiElement("Testmode - won't actually import notes").SetClass("alert"), undefined, state.featureSwitchIsTesting), + new Toggle(t.testMode.SetClass("block alert"), undefined, state.featureSwitchIsTesting), LanguagePicker.CreateLanguagePicker(Translations.t.importHelper.title.SupportedLanguages())?.SetClass("mt-4 self-end flex-col"), ].map(el => el?.SetClass("pl-4")) diff --git a/UI/ImportFlow/Introdution.ts b/UI/ImportFlow/Introdution.ts index 5cd65721b..e46dd3c9f 100644 --- a/UI/ImportFlow/Introdution.ts +++ b/UI/ImportFlow/Introdution.ts @@ -3,18 +3,42 @@ import {FlowStep} from "./FlowStep"; import {UIEventSource} from "../../Logic/UIEventSource"; import Translations from "../i18n/Translations"; import Title from "../Base/Title"; +import {CreateNotes} from "./CreateNotes"; export default class Introdution extends Combine implements FlowStep { - readonly IsValid: UIEventSource = new UIEventSource(true); - readonly Value: UIEventSource = new UIEventSource(undefined); + readonly IsValid: UIEventSource; + readonly Value: UIEventSource; constructor() { + const example = CreateNotes.createNoteContents({ + properties:{ + "some_key":"some_value", + "note":"a note in the original dataset" + }, + geometry:{ + coordinates: [3.4,51.2] + } + }, { + wikilink: "https://wiki.openstreetmap.org/wiki/Imports/", + intro: "There might be an XYZ here", + theme: "theme", + source: "source of the data" + }) + super([ new Title(Translations.t.importHelper.introduction.title), Translations.t.importHelper.introduction.description, Translations.t.importHelper.introduction.importFormat, + new Combine( + [new Combine( + example + ).SetClass("flex flex-col") + ] ).SetClass("literal-code") ]); this.SetClass("flex flex-col") + this. IsValid= new UIEventSource(true); + this. Value = new UIEventSource(undefined); + } } \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index b0d3ea464..953bad7a4 100644 --- a/langs/en.json +++ b/langs/en.json @@ -119,6 +119,7 @@ "title": "Download visible data", "uploadGpx": "Upload your track to OpenStreetMap" }, + "error": "Something went wrong...", "example": "Example", "examples": "Examples", "fewChangesBefore": "Please, answer a few questions of existing points before adding a new point.", @@ -151,6 +152,7 @@ "next": "Next", "noNameCategory": "{category} without a name", "noTagsSelected": "No tags selected", + "notValid": "Select a valid value to continue", "number": "number", "oneSkippedQuestion": "One question is skipped", "openStreetMapIntro": "

An Open Map

One that everyone can use and edit freely. A single place to store all geo-info. Different, small, incompatible and outdated maps are not needed anywhere.

OpenStreetMap is not the enemy map. The map data can be used freely (with attribution and publication of changes to that data). Everyone can add new data and fix errors. This website uses OpenStreetMap. All the data is from there, and your answers and corrections are used all over.

Many people and apps already use OpenStreetMap: Organic Maps, OsmAnd, but also the maps at Facebook, Instagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap.

", @@ -276,6 +278,18 @@ "willBePublished": "Your picture will be published " }, "importHelper": { + "askMetadata": { + "downloadGeojson": "Download geojson", + "giveDescription": "Please, write a small description for someone who sees the note. A good note describes what the contributor has to do, e.g; There might be a bench here. If you are around, could you please check and indicate if the bench exists or not? (A link to MapComplete will be added automatically)", + "giveSource": "What is the source of this data? If 'source' is set in the feature, this value will be ignored", + "giveWikilink": "On what wikipage can one find more information about this import?", + "intro": "Before adding {count} notes, please provide some extra information.", + "orDownload": "Alternatively, you can download the dataset to import directly", + "shouldBeOsmWikilink": "Expected a link to a page on wiki.openstreetmap.org", + "shouldBeUrl": "Not a valid URL", + "shouldNotBeHomepage": "Nope, the home page isn't allowed either. Enter the URL of a proper wikipage documenting your import", + "title": "Set metadata" + }, "compareToAlreadyExistingNotes": { "completelyImported": "All of the proposed points have (or had) an import note already", "loading": "Fetching notes from OSM", @@ -288,13 +302,27 @@ "titleLong": "Compare with already existing 'to-import'-notes", "wontBeImported": "These data points will not be imported and are shown as red dots on the map below" }, - "inspectDidAutoDected": "Layer was chosen automatically", + "confirmProcess": { + "contactedCommunity": "I did contact the (local) community about this import", + "licenseIsCompatible": "The license of the data to import allows it to be imported into OSM. They are allowed to be redistributed commercially, with only minimal attribution", + "readImportGuidelines": "I have read the import guidelines on the OSM wiki", + "title": "License and community", + "titleLong": "Did you go through the import process?", + "wikipageIsMade": "The process is documented on the OSM-wiki (you'll need this link later)" + }, + "createNotes": { + "creating": "Created {count} notes out of {total}", + "done": "All {count} notes have been created!", + "loading": "Hang on while we are loading...", + "openImportViewer": "Inspect the progress of your notes in the 'import_viewer'", + "title": "Note creation" + }, + "gotoImportViewer": "Inspect your previous imports", "introduction": { "description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.", - "importFormat": "A text in a note should have the following format in order to be picked up:
[A bit of introduction]
https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import
[all tags of the feature]
", + "importFormat": "A text in a note should have the following format in order to be picked up", "title": "Introduction" }, - "locked": "You need at least {importHelperUnlock} to use the import helper", "login": { "lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.", "loggedInWith": "You are currently logged in as {name} and have made {csCount} changesets", @@ -310,6 +338,11 @@ "selectLayer": "Which layer does this import match with?", "title": "Map preview" }, + "noteParts": { + "datasource": "Original data from {source}", + "importEasily": "Add this point easily with MapComplete:", + "wikilink": "More information about this import can be found at {wikilink}" + }, "previewAttributes": { "allAttributesSame": "All features to import have this tag", "inspectDataTitle": "Inspect data of {count} features to import", @@ -342,8 +375,8 @@ "title": "Select a theme", "unmatchedTitle": "The following elements don't match any of the presets" }, - "title": "Import helper", - "validateDataTitle": "Validate data" + "testMode": "Testmode - won't actually import notes", + "title": "Import helper" }, "importInspector": { "title": "Inspect and manage import notes" From 0d81decdc7828f844feee0ccd70fcf24aa69ead2 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Apr 2022 19:46:14 +0200 Subject: [PATCH 3/7] Further translations of the import helper --- .../Conversion/AddContextToTranslations.ts | 44 ++++++++++++++++ UI/ImportFlow/ConflationChecker.ts | 51 +++++++++++-------- UI/ImportFlow/ImportHelperGui.ts | 2 +- UI/Input/InputElementWrapper.ts | 3 +- assets/welcome_message.json | 6 +++ langs/en.json | 21 ++++++++ 6 files changed, 105 insertions(+), 22 deletions(-) diff --git a/Models/ThemeConfig/Conversion/AddContextToTranslations.ts b/Models/ThemeConfig/Conversion/AddContextToTranslations.ts index 8d9a95390..9e3dc8c56 100644 --- a/Models/ThemeConfig/Conversion/AddContextToTranslations.ts +++ b/Models/ThemeConfig/Conversion/AddContextToTranslations.ts @@ -39,6 +39,39 @@ export class AddContextToTranslations extends DesugaringStep { * } * rewritten // => expected * + * // should use the ID if one is present instead of the index + * const theme = { + * layers: [ + * { + * tagRenderings:[ + * + * {id: "some-tr", + * question:{ + * en:"Question?" + * } + * } + * ] + * } + * ] + * } + * const rewritten = new AddContextToTranslations("prefix:").convert(theme, "context").result + * const expected = { + * layers: [ + * { + * tagRenderings:[ + * + * {id: "some-tr", + * question:{ + * _context: "prefix:context.layers.0.tagRenderings.some-tr.question" + * en:"Question?" + * } + * } + * ] + * } + * ] + * } + * rewritten // => expected + * * // should preserve nulls * const theme = { * layers: [ @@ -70,6 +103,17 @@ export class AddContextToTranslations extends DesugaringStep { return leaf } if (typeof leaf === "object") { + + // follow the path. If we encounter a number, check that there is no ID we can use instead + let breadcrumb = json; + for (let i = 0; i < path.length; i++) { + const pointer = path[i] + breadcrumb = breadcrumb[pointer] + if(pointer.match("[0-9]+") && breadcrumb["id"] !== undefined){ + path[i] = breadcrumb["id"] + } + } + return {...leaf, _context: this._prefix + context + "." + path.join(".")} } else { return leaf diff --git a/UI/ImportFlow/ConflationChecker.ts b/UI/ImportFlow/ConflationChecker.ts index f0a3f17c9..0f01ce3c9 100644 --- a/UI/ImportFlow/ConflationChecker.ts +++ b/UI/ImportFlow/ConflationChecker.ts @@ -7,7 +7,6 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import Constants from "../../Models/Constants"; import RelationsTracker from "../../Logic/Osm/RelationsTracker"; import {VariableUiElement} from "../Base/VariableUIElement"; -import {FixedUiElement} from "../Base/FixedUiElement"; import {FlowStep} from "./FlowStep"; import Loading from "../Base/Loading"; import {SubtleButton} from "../Base/SubtleButton"; @@ -28,6 +27,7 @@ import * as import_candidate from "../../assets/layers/import_candidate/import_c import {GeoOperations} from "../../Logic/GeoOperations"; import FeatureInfoBox from "../Popup/FeatureInfoBox"; import {ImportUtils} from "./ImportUtils"; +import Translations from "../i18n/Translations"; /** * Given the data to import, the bbox and the layer, will query overpass for similar items @@ -190,6 +190,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea features: toImportWithNearby }) + const t = Translations.t.importHelper.conflationChecker const conflationMaps = new Combine([ new VariableUiElement( @@ -197,7 +198,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea if (geojson === undefined) { return undefined; } - return new SubtleButton(Svg.download_svg(), "Download the loaded geojson from overpass").onClick(() => { + return new SubtleButton(Svg.download_svg(), t.downloadOverpassData).onClick(() => { Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, " "), "mapcomplete-" + layer.id + ".geojson", { mimetype: "application/json+geo" }) @@ -208,43 +209,53 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea return undefined; } if (age < 0) { - return new FixedUiElement("Cache was expired") + return t.cacheExpired } - return new FixedUiElement("Loaded data is from the cache and is " + Utils.toHumanTime(age) + " old") + return t.loadedDataAge.Subs({age: Utils.toHumanTime(age)}) })), - new Title("Live data on OSM"), - "The "+toImport.features.length+" red elements on the following map are all your import candidates.", - new VariableUiElement(geojson.map(geojson => new FixedUiElement((geojson?.features?.length ?? "No") + " elements are loaded from OpenStreetMap which match the layer "+layer.id+". Zooming in might be needed to show them"))), + new Title(t.titleLive), + t.importCandidatesCount.Subs({count:toImport.features.length }), + new VariableUiElement(geojson.map(geojson => { + if(geojson?.features?.length === undefined && geojson?.features?.length === 0){ + return t.nothingLoaded.Subs(layer).SetClass("alert") + } + return new Combine([ + t.osmLoaded.Subs({count: geojson.features.length, name: layer.name}), + + ]) + })), osmLiveData, - new Combine(["The live data is shown if the zoomlevel is at least ", zoomLevel, ". The current zoom level is ", new VariableUiElement(osmLiveData.location.map(l => "" + l.zoom))]).SetClass("flex"), - - new Title("Nearby features"), - new Combine(["The following map shows features to import which have an OSM-feature within ", nearbyCutoff, "meter"]).SetClass("flex"), + new VariableUiElement(osmLiveData.location.map(location => { + return t.zoomIn.Subs({needed:zoomLevel, current: location.zoom }) + } )), + new Title(t.titleNearby), + new Combine([t.mapShowingNearbyIntro, nearbyCutoff]).SetClass("flex"), new VariableUiElement(toImportWithNearby.features.map(feats => - new FixedUiElement("The "+ feats.length +" red elements on the following map will not be imported!").SetClass("alert"))), - "Set the range to 0 or 1 if you want to import them all", + t.nearbyWarn.Subs({count: feats.length}).SetClass("alert"))), + ,t.setRangeToZero, matchedFeaturesMap]).SetClass("flex flex-col") super([ - new Title("Comparison with existing data"), + new Title(t.title), new VariableUiElement(overpassStatus.map(d => { if (d === "idle") { - return new Loading("Checking local storage...") - } - if (d["error"] !== undefined) { - return new FixedUiElement("Could not load latest data from overpass: " + d["error"]).SetClass("alert") + return new Loading(t.states.idle) } if (d === "running") { - return new Loading("Querying overpass...") + return new Loading(t.states.running) } + if (d["error"] !== undefined) { + return t.states.error.Subs(d).SetClass("alert") + } + if (d === "cached") { return conflationMaps } if (d === "success") { return conflationMaps } - return new FixedUiElement("Unexpected state " + d).SetClass("alert") + return t.states.unexpected.Subs({state: d}).SetClass("alert") })) ]) diff --git a/UI/ImportFlow/ImportHelperGui.ts b/UI/ImportFlow/ImportHelperGui.ts index 82cd8684f..000cae352 100644 --- a/UI/ImportFlow/ImportHelperGui.ts +++ b/UI/ImportFlow/ImportHelperGui.ts @@ -36,7 +36,7 @@ export default class ImportHelperGui extends LeftIndex { .then(t.mapPreview, geojson => new MapPreview(state, geojson)) .then(t.selectTheme, v => new SelectTheme(v)) .then(t.compareToAlreadyExistingNotes, v => new CompareToAlreadyExistingNotes(state, v)) - .then("Compare with existing data", v => new ConflationChecker(state, v)) + .then(t.conflationChecker, v => new ConflationChecker(state, v)) .then(t.confirmProcess, v => new ConfirmProcess(v)) .then(t.askMetadata, (v) => new AskMetadata(v)) .finish(t.createNotes.title, v => new CreateNotes(state, v)); diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts index 9006956c5..623fc18d5 100644 --- a/UI/Input/InputElementWrapper.ts +++ b/UI/Input/InputElementWrapper.ts @@ -9,7 +9,8 @@ export default class InputElementWrapper extends InputElement { private readonly _inputElement: InputElement; private readonly _renderElement: BaseUIElement - constructor(inputElement: InputElement, translation: Translation, key: string, tags: UIEventSource, state: FeaturePipelineState) { + constructor(inputElement: InputElement, translation: Translation, key: string, + tags: UIEventSource, state: FeaturePipelineState) { super() this._inputElement = inputElement; const mapping = new Map() diff --git a/assets/welcome_message.json b/assets/welcome_message.json index 0d53ded19..6b0117b6e 100644 --- a/assets/welcome_message.json +++ b/assets/welcome_message.json @@ -1,4 +1,10 @@ [ + { + "start_date": "2022-05-30", + "end_date":"2022-06-05", + "message": "The 3rd of June is World Bicycle Day. Go find a bike shop or bike pump nearby", + "featured_theme": "cyclofix" + }, { "start_date": "2022-04-18", "end_date": "2022-04-24", diff --git a/langs/en.json b/langs/en.json index 953bad7a4..3d39a5355 100644 --- a/langs/en.json +++ b/langs/en.json @@ -310,6 +310,27 @@ "titleLong": "Did you go through the import process?", "wikipageIsMade": "The process is documented on the OSM-wiki (you'll need this link later)" }, + "conflationChecker": { + "cacheExpired": "Cache was expired", + "downloadOverpassData": "Download the loaded geojson from overpass", + "importCandidatesCount": "The {count} red elements on the following map are all your import candidates.", + "loadedDataAge": "Loaded data is from the cache and is {age} old", + "mapShowingNearbyIntro": "The following map shows features to import which have an OSM-feature within ", + "nearbyWarn": "The {count} red elements on the following map will not be imported!", + "nothingLoaded": "No elements are loaded from OpenStreetMap which match the current layer {name}", + "osmLoaded": "{count} elements are loaded from OpenStreetMap which match the layer {name}.", + "setRangeToZero": "Set the range to 0 or 1 if you want to import them all", + "states": { + "error": "Could not load latest data from overpass due to {error}", + "idle": "Checking local storage...", + "running": "Querying overpass...", + "unexpected": "Unexpected state {state}" + }, + "title": "Compare with existing data", + "titleLive": "Live data on OSM", + "titleNearby": "Nearby features", + "zoomIn": "The live data is shown if the zoomlevel is at least {needed}. The current zoom level is {current}" + }, "createNotes": { "creating": "Created {count} notes out of {total}", "done": "All {count} notes have been created!", From 6f9aa9b708eb401cfe4f319bf51610787f78d572 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Apr 2022 19:49:40 +0200 Subject: [PATCH 4/7] Add call for tranlations to featured messages --- assets/welcome_message.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/welcome_message.json b/assets/welcome_message.json index 6b0117b6e..31f736510 100644 --- a/assets/welcome_message.json +++ b/assets/welcome_message.json @@ -2,9 +2,14 @@ { "start_date": "2022-05-30", "end_date":"2022-06-05", - "message": "The 3rd of June is World Bicycle Day. Go find a bike shop or bike pump nearby", + "message": "The 3rd of June is World Bicycle DayX. Go find a bike shop or bike pump nearby", "featured_theme": "cyclofix" }, + { + "start_date": "2022-04-24", + "end_date": "2022-05-30", + "message": "Help translating MapComplete! If you have some free time, please translate MapComplete to your favourite language. Read the instructions here" + }, { "start_date": "2022-04-18", "end_date": "2022-04-24", From 2ee1f6baeeb7cff36a9415a10fb1932120ded06c Mon Sep 17 00:00:00 2001 From: kjon Date: Wed, 13 Apr 2022 20:15:31 +0000 Subject: [PATCH 5/7] Translated using Weblate (German) Currently translated at 99.7% (451 of 452 strings) Translation: MapComplete/Core Translate-URL: https://hosted.weblate.org/projects/mapcomplete/core/de/ --- langs/de.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/langs/de.json b/langs/de.json index 4ed72831c..0c28eca83 100644 --- a/langs/de.json +++ b/langs/de.json @@ -39,7 +39,7 @@ }, "general": { "about": "OpenStreetMap für ein bestimmtes Thema einfach bearbeiten und hinzufügen", - "aboutMapcomplete": "

Über MapComplete

Nutzen Sie es, um OpenStreetMap-Informationen zu einem einzigen Thema hinzuzufügen. Beantworten Sie Fragen, und innerhalb weniger Minuten sind Ihre Beiträge überall verfügbar. Der Theme-Maintainer definiert Elemente, Fragen und Sprachen dafür.

Mehr erfahren

MapComplete bietet immer den nächsten Schritt, um mehr über OpenStreetMap zu erfahren.

  • Wenn es in eine Website eingebettet wird, verlinkt der iframe zu einer Vollbildversion von MapComplete
  • Die Vollbildversion bietet Infos über OpenStreetMap
  • Das Betrachten funktioniert ohne Anmeldung, aber das Bearbeiten erfordert ein OSM-Konto.
  • Wenn Sie nicht angemeldet sind, werden Sie dazu aufgefordert
  • Sobald Sie eine Frage beantwortet haben, können Sie der Karte neue Punkte hinzufügen
  • Nach einer Weile werden aktuelle OSM-Tags angezeigt, die später mit dem Wiki verlinkt werden


Haben Sie ein Problem bemerkt? Haben Sie einen Funktionswunsch? Möchten Sie bei der Übersetzung helfen? Besuchen Sie den Quellcode oder den Issue Tracker

Möchten Sie Ihren Fortschritt sehen? Verfolgen Sie die Anzahl der Änderungen auf OsmCha.

", + "aboutMapcomplete": "

Über MapComplete

Nutzen Sie MapComplete, um OpenStreetMap-Informationen zu einem einzigen Thema hinzuzufügen. Beantworten Sie Fragen, und in wenigen Minuten sind Ihre Beiträge überall verfügbar. Der Theme-Maintainer definiert Elemente, Fragen und Sprachen dafür.

Mehr erfahren

MapComplete bietet immer den nächsten Schritt, um mehr über OpenStreetMap zu erfahren.

  • In einer Website eingebettet, verlinkt der iframe zu einer Vollbildversion von MapComplete
  • Die Vollbildversion bietet Informationen über OpenStreetMap
  • Das Betrachten funktioniert ohne Anmeldung, aber das Bearbeiten erfordert ein OSM-Konto.
  • Wenn Sie nicht angemeldet sind, werden Sie dazu aufgefordert
  • Sobald Sie eine Frage beantwortet haben, können Sie der Karte neue Punkte hinzufügen
  • Nach einer Weile werden aktuelle OSM-Tags angezeigt, die später mit dem Wiki verlinkt werden


Haben Sie ein Problem bemerkt? Haben Sie einen Funktionswunsch? Möchten Sie bei der Übersetzung helfen? Hier geht es zum Quellcode und Issue Tracker

Möchten Sie Ihren Fortschritt sehen? Verfolgen Sie Ihre Änderungen auf OsmCha.

", "add": { "addNew": "Füge {category} hinzu", "addNewMapLabel": "Hier klicken, um ein neues Element hinzuzufügen", @@ -76,9 +76,9 @@ "attribution": { "attributionContent": "

Alle Daten wurden bereitgestellt von OpenStreetMap, frei verwendbar unter der Open Database License.

", "attributionTitle": "Danksagung", - "codeContributionsBy": "MapComplete wurde von {contributors} und {hiddenCount} weiteren Beitragenden erstellt", - "donate": "Unterstütze MapComplete finanziell", - "editId": "Den OpenStreetMap online Editor hier öffnen", + "codeContributionsBy": "MapComplete wurde erstellt von {contributors} und {hiddenCount} weiteren Beitragenden", + "donate": "MapComplete finanziell unterstützen", + "editId": "Den OpenStreetMap Editor öffnen", "editJosm": "Bearbeite mit JOSM", "iconAttribution": { "title": "Verwendete Symbole" @@ -86,12 +86,12 @@ "josmNotOpened": "JOSM konnte nicht erreicht werden. Stelle sicher, dass es geöffnet ist und Fernkontrolle aktiviert ist", "josmOpened": "JOSM ist geöffnet", "mapContributionsBy": "Die aktuell sichtbaren Daten wurden editiert durch {contributors}", - "mapContributionsByAndHidden": "Die aktuell sichtbaren Daten wurden editiert durch {contributors} und {hiddenCount} weitere Beitragende", - "openIssueTracker": "Melde einen Fehler", - "openMapillary": "Öffne Mapillary hier", + "mapContributionsByAndHidden": "Die aktuell sichtbaren Daten wurden editiert von {contributors} und {hiddenCount} weiteren Beitragenden", + "openIssueTracker": "Einen Fehler melden", + "openMapillary": "Mapillary öffnen", "openOsmcha": "Letzte Bearbeitungen mit {theme} ansehen", "themeBy": "Thema betreut von {author}", - "translatedBy": "MapComplete wurde von {contributors}, {hiddenCount} und weiteren Mitwirkenden übersetzt." + "translatedBy": "MapComplete wurde übersetzt von {contributors}, {hiddenCount} und weiteren Mitwirkenden" }, "back": "Zurück", "backToMapcomplete": "Zurück zur Themenwahl", @@ -153,7 +153,7 @@ "noTagsSelected": "Keine Tags ausgewählt", "number": "Zahl", "oneSkippedQuestion": "Eine Frage wurde übersprungen", - "openStreetMapIntro": "

Eine offene Karte

Eine Karte, die jeder frei nutzen und bearbeiten kann. Ein einziger Ort, um alle Geoinformationen zu speichern. Unterschiedliche, kleine, inkompatible und veraltete Karten werden nirgendwo gebraucht.

OpenStreetMap ist nicht die feindliche Karte. Die Kartendaten können frei verwendet werden (mit Benennung und Veröffentlichung von Änderungen an diesen Daten). Jeder kann neue Daten hinzufügen und Fehler korrigieren. Diese Webseite nutzt OpenStreetMap. Alle Daten stammen von dort, und Ihre Antworten und Korrekturen werden überall verwendet.

Viele Menschen und Anwendungen nutzen bereits OpenStreetMap: Organic Maps, OsmAnd, aber auch die Karten bei Facebook, Instagram, Apple-maps und Bing-maps werden (teilweise) von OpenStreetMap bereichert.

", + "openStreetMapIntro": "

Eine offene Karte

Eine Karte, die jeder frei nutzen und bearbeiten kann. Ein einziger Ort, um alle Geoinformationen zu speichern. Unterschiedliche, kleine, inkompatible und veraltete Karten werden nirgendwo gebraucht.

OpenStreetMap ist nicht die feindliche Karte. Die Kartendaten können frei verwendet werden (mit Benennung und Veröffentlichung von Änderungen an diesen Daten). Jeder kann neue Daten hinzufügen und Fehler korrigieren. Diese Webseite nutzt OpenStreetMap. Alle Daten stammen von dort, und Ihre Antworten und Korrekturen werden überall verwendet.

Viele Menschen und Anwendungen nutzen bereits OpenStreetMap: Organic Maps, OsmAnd; auch die Kartendaten von Facebook, Instagram, Apple-maps und Bing-maps stammen (teilweise) von OpenStreetMap.

", "openTheMap": "Karte öffnen", "opening_hours": { "closed_permanently": "Geschlossen auf unbestimmte Zeit", @@ -205,11 +205,11 @@ "editThisTheme": "Dieses Thema bearbeiten", "embedIntro": "

Auf Ihrer Website einbetten

Bitte betten Sie diese Karte in Ihre Webseite ein.
Wir ermutigen Sie, es zu tun - Sie müssen nicht einmal um Erlaubnis fragen.
Es ist kostenlos und wird es immer sein. Je mehr Leute sie benutzen, desto wertvoller wird sie.", "fsAddNew": "Schaltfläche 'neuen POI hinzufügen' aktivieren", - "fsGeolocation": "Die Schaltfläche 'Mich geolokalisieren' aktivieren (nur für Mobil)", - "fsIncludeCurrentBackgroundMap": "Den aktuellen Hintergrund übernehmen {name}", - "fsIncludeCurrentLayers": "Die aktuelle Ebenenauswahl übernehmen", + "fsGeolocation": "Schaltfläche 'Mich geolokalisieren' aktivieren (nur mobil)", + "fsIncludeCurrentBackgroundMap": "Aktuellen Hintergrund übernehmen {name}", + "fsIncludeCurrentLayers": "Aktuelle Ebenenauswahl übernehmen", "fsIncludeCurrentLocation": "Aktuelle Position einbeziehen", - "fsLayerControlToggle": "Mit der ausgeklappter Ebenensteuerung beginnen", + "fsLayerControlToggle": "Ausgeklappte Ebenensteuerung anzeigen", "fsLayers": "Ebenensteuerung aktivieren", "fsSearch": "Suchleiste aktivieren", "fsUserbadge": "Anmeldeschaltfläche aktivieren", @@ -390,16 +390,16 @@ "warnAnonymous": "Sie sind nicht eingeloggt. Wir sind nicht in der Lage, Sie zu kontaktieren, um Ihr Problem zu lösen." }, "privacy": { - "editing": "Wenn Sie eine Änderung an der Karte vornehmen, wird diese Änderung auf OpenStreetMap aufgezeichnet und ist für jeden öffentlich zugänglich. Ein mit MapComplete vorgenommener Änderungssatz enthält die folgenden Daten:
  • Die von Ihnen vorgenommenen Änderungen
  • Ihr Benutzername
  • Wann diese Änderung vorgenommen wurde
  • Das Thema, das Sie bei der Änderung verwendet haben
  • Die Sprache der Benutzeroberfläche
  • Eine Angabe darüber, wie nah Sie an geänderten Objekten waren. Andere Kartierer können diese Informationen nutzen, um festzustellen, ob eine Änderung auf Basis einer Vor-Ort Erkundung oder einer Fernerkundung vorgenommen wurde
Ausführliche Informationen finden Sie in den Datenschutzbestimmungen auf OpenStreetMap.org. Wir möchten Sie daran erinnern, dass Sie bei der Anmeldung einen fiktiven Namen verwenden können.", + "editing": "Wenn Sie Änderungen an der Karte vornehmen, werden diese auf OpenStreetMap gespeichert und sind für jeden öffentlich zugänglich. Ein mit MapComplete erstellter Änderungssatz enthält folgende Daten:
  • Die von Ihnen vorgenommenen Änderungen
  • Ihren Benutzernamen
  • Den Zeitpunkt Ihrer Änderungen
  • Das MapComplete-Thema, das Sie bei Ihren Änderungen verwendet haben
  • Die Sprache Ihrer Benutzeroberfläche
  • Eine Angabe darüber, wie nah Sie an geänderten Objekten waren. Andere Kartierer können diese Informationen nutzen, um festzustellen, ob eine Änderung auf Basis einer Vor-Ort Erkundung oder einer Fernerkundung vorgenommen wurde
Ausführliche Informationen finden Sie in den Datenschutzbestimmungen auf OpenStreetMap.org. Wir möchten Sie daran erinnern, dass Sie bei der Anmeldung einen fiktiven Namen verwenden können.", "editingTitle": "Ihre Änderungen", - "geodata": "Wenn MapComplete Ihre Standortdaten erhält, verbleiben Ihre Daten zum aktuellen Standort und zuvor besuchter Orte auf Ihrem Gerät. Ihre Standortdaten werden niemals automatisch an eine andere Stelle gesendet - es sei denn, eine Funktion gibt eindeutig etwas anderes an.", + "geodata": "Wenn MapComplete Ihre aktuellenStandortdaten ermittelt, verbleiben diese wie auch zuvor ermittelte Standorte auf Ihrem Gerät. Ihre Standortdaten werden niemals automatisch an eine andere Stelle gesendet - es sei denn, eine Funktion gibt eindeutig etwas anderes an.", "geodataTitle": "Ihr Standort", "intro": "Privatsphäre ist wichtig - sowohl für den Einzelnen als auch für die Gesellschaft. MapComplete versucht, Ihre Privatsphäre so weit wie möglich zu respektieren - bis zu dem Punkt, an dem kein lästiger Cookie-Banner mehr nötig ist. Dennoch möchten wir Sie darüber informieren, welche Informationen gesammelt und weitergegeben werden, unter welchen Umständen und warum diese Kompromisse gemacht werden.", "miscCookies": "MapComplete integriert verschiedene andere Dienste, insbesondere um Bilder von Objekten zu laden. Bilder werden auf verschiedenen Servern von Drittanbietern gehostet, die möglicherweise eigene Cookies setzen.", "miscCookiesTitle": "Andere Cookies", "surveillance": "Da Sie die Datenschutzbestimmungen lesen, ist Ihnen der Datenschutz wahrscheinlich wichtig - uns auch! Wir haben sogar ein Thema gemacht, das Überwachungskameras zeigt. Zögern Sie nicht, sie alle zu mappen!", "title": "Datenschutzbestimmungen", - "tracking": "Um einen Einblick zu bekommen, wer unsere Website besucht, werden einige technische Informationen gesammelt. Dazu gehören das Land, aus dem Sie die Webseite besucht haben, die Webseite, die Sie auf MapComplete verwiesen hat, der Typ Ihres Geräts und die Bildschirmgröße. Ein Cookie wird auf Ihrem Gerät platziert, um anzuzeigen, dass Sie MapComplete heute bereits besucht haben. Diese Daten sind nicht detailliert genug, um Sie persönlich zu identifizieren. Diese Statistiken sind nur in aggregierter Form für jedermann zugänglich und sind öffentlich für jedermann zugänglich", + "tracking": "Um einen Einblick zu bekommen, wer unsere Website besucht, werden einige technische Informationen gesammelt. Dazu gehören das Land, aus dem Sie die Webseite besucht haben, die Webseite, die Sie auf MapComplete verwiesen hat, der Typ Ihres Geräts und die Bildschirmgröße. Ein Cookie wird auf Ihrem Gerät platziert, um anzuzeigen, dass Sie MapComplete heute bereits besucht haben. Diese Daten sind nicht detailliert genug, um Sie persönlich zu identifizieren. Diese Statistiken sind nur in aggregierter Form öffentlich für jedermann zugänglich", "trackingTitle": "Statistische Daten", "whileYoureHere": "Ist Ihnen die Privatsphäre wichtig?" }, @@ -530,7 +530,7 @@ "splitTitle": "Wählen Sie auf der Karte aus, wo die Straße geteilt werden soll" }, "translations": { - "activateButton": "Bei der Übersetzung von MapComplete helfen", + "activateButton": "MapComplete übersetzen", "completeness": "Die Übersetzungen für {theme} in {language} ist zu {percentage}% vollständig: {translated} Zeichenfolgen von {total} sind übersetzt", "deactivate": "Schaltflächen für die Übersetzung deaktivieren", "help": "Klicken Sie auf das 'translate'-Symbol neben einer Zeichenfolge, um einen Text einzugeben oder zu aktualisieren. Dazu benötigen Sie einen Weblate-Account. Erstellen Sie einen mit Ihrem OSM-Benutzernamen, um den Übersetzungsmodus automatisch freizuschalten.", From fce30e357c4397a83e257b6f9d380084ab5bd474 Mon Sep 17 00:00:00 2001 From: kjon Date: Wed, 13 Apr 2022 19:44:07 +0000 Subject: [PATCH 6/7] Translated using Weblate (German) Currently translated at 100.0% (437 of 437 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/de/ --- langs/themes/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/langs/themes/de.json b/langs/themes/de.json index 27fe7c1e6..ddb21cabf 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -1141,7 +1141,7 @@ "playgrounds": { "description": "Auf dieser Karte finden Sie Spielplätze und können weitere Informationen hinzufügen", "shortDescription": "Eine Karte mit Spielplätzen", - "title": "Spielpläzte" + "title": "Spielplätze" }, "postal_codes": { "description": "Postleitzahlen", @@ -1326,4 +1326,4 @@ "shortDescription": "Eine Karte mit Abfalleimern", "title": "Abfalleimer" } -} \ No newline at end of file +} From 4ebd96e8f2d70129a81231f195c04609877ad76e Mon Sep 17 00:00:00 2001 From: Win Olario Date: Thu, 14 Apr 2022 04:19:53 +0000 Subject: [PATCH 7/7] Translated using Weblate (Filipino) Currently translated at 19.0% (95 of 498 strings) Translation: MapComplete/Core Translate-URL: https://hosted.weblate.org/projects/mapcomplete/core/fil/ --- langs/fil.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/langs/fil.json b/langs/fil.json index ba6343c65..30ae52313 100644 --- a/langs/fil.json +++ b/langs/fil.json @@ -40,7 +40,15 @@ "about": "Madaling i-edit at mag-dagdag sa OpenStreetMap gamit ang mga partikular na tikha", "add": { "addNew": "Dagdagan ng {category}", - "addNewMapLabel": "I-click ito para mag-dagdag ng bagong bagay" + "addNewMapLabel": "I-click ito para mag-dagdag ng bagong bagay", + "disableFilters": "Huwag paganahin ang lahat ng filter", + "import": { + "hasBeenImported": "Ang object na ito ay nai-angkat na" + }, + "disableFiltersExplanation": "May mga tampók na maaring nai-tago ng filter", + "hasBeenImported": "Ang bukóng ito ay nai-angkat na", + "confirmIntro": "

Mag-dagdag ng {title}?

Ang tampók na ida-dagdag mo ay makikita ng lahat. Paki-usap, mag-dagdag lamang ng mga bagay na tutuong umiiral. Marami pang mga aplikasyon ang gumagamit ng datos na ito.", + "confirmButton": "Magdagdag ng {category}
Makikita ng lahat ang idinagdag mo
" } } }