Add translation buttons
This commit is contained in:
parent
592bc4ae0b
commit
2c7fb556dc
31 changed files with 442 additions and 150 deletions
|
@ -11,6 +11,7 @@ import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater";
|
||||||
import {Changes} from "../Osm/Changes";
|
import {Changes} from "../Osm/Changes";
|
||||||
import ChangeToElementsActor from "../Actors/ChangeToElementsActor";
|
import ChangeToElementsActor from "../Actors/ChangeToElementsActor";
|
||||||
import PendingChangesUploader from "../Actors/PendingChangesUploader";
|
import PendingChangesUploader from "../Actors/PendingChangesUploader";
|
||||||
|
import * as translators from "../../assets/translators.json"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
|
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
|
||||||
|
@ -36,6 +37,8 @@ export default class UserRelatedState extends ElementsState {
|
||||||
*/
|
*/
|
||||||
public favouriteLayers: UIEventSource<string[]>;
|
public favouriteLayers: UIEventSource<string[]>;
|
||||||
|
|
||||||
|
public readonly isTranslator : UIEventSource<boolean>;
|
||||||
|
|
||||||
constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) {
|
constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) {
|
||||||
super(layoutToUse);
|
super(layoutToUse);
|
||||||
|
|
||||||
|
@ -50,6 +53,21 @@ export default class UserRelatedState extends ElementsState {
|
||||||
osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data,
|
osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data,
|
||||||
attemptLogin: options?.attemptLogin
|
attemptLogin: options?.attemptLogin
|
||||||
})
|
})
|
||||||
|
this.isTranslator = this.osmConnection.userDetails.map(ud => {
|
||||||
|
if(!ud.loggedIn){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const name= ud.name.toLowerCase().replace(/\s+/g, '')
|
||||||
|
return translators.contributors.some(c => c.contributor.toLowerCase().replace(/\s+/g, '') === name)
|
||||||
|
})
|
||||||
|
this.isTranslator.addCallbackAndRunD(ud => {
|
||||||
|
if(ud){
|
||||||
|
Locale.showLinkToWeblate.setData(true)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QueryParameters.GetBooleanQueryParameter("fs-translation-mode",false,"If set, will show the translation buttons")
|
||||||
|
.addCallbackAndRunD(tr => Locale.showLinkToWeblate.setData(Locale.showLinkToWeblate.data || tr))
|
||||||
|
|
||||||
|
|
||||||
this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false)
|
this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false)
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default class ExtraLinkConfig {
|
||||||
|
|
||||||
constructor(configJson: ExtraLinkConfigJson, context) {
|
constructor(configJson: ExtraLinkConfigJson, context) {
|
||||||
this.icon = configJson.icon
|
this.icon = configJson.icon
|
||||||
this.text = Translations.T(configJson.text)
|
this.text = Translations.T(configJson.text, "themes:"+context+".text")
|
||||||
this.href = configJson.href
|
this.href = configJson.href
|
||||||
this.newTab = configJson.newTab
|
this.newTab = configJson.newTab
|
||||||
this.requirements = new Set(configJson.requirements)
|
this.requirements = new Set(configJson.requirements)
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default class FilterConfig {
|
||||||
this.id = json.id;
|
this.id = json.id;
|
||||||
let defaultSelection : number = undefined
|
let defaultSelection : number = undefined
|
||||||
this.options = json.options.map((option, i) => {
|
this.options = json.options.map((option, i) => {
|
||||||
const ctx = `${context}.options[${i}]`;
|
const ctx = `${context}.options.${i}`;
|
||||||
const question = Translations.T(
|
const question = Translations.T(
|
||||||
option.question,
|
option.question,
|
||||||
`${ctx}.question`
|
`${ctx}.question`
|
||||||
|
|
|
@ -72,6 +72,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
official: boolean = true
|
official: boolean = true
|
||||||
) {
|
) {
|
||||||
context = context + "." + json.id;
|
context = context + "." + json.id;
|
||||||
|
const translationContext = "layers:"+json.id
|
||||||
super(json, context)
|
super(json, context)
|
||||||
this.id = json.id;
|
this.id = json.id;
|
||||||
|
|
||||||
|
@ -125,7 +126,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
|
|
||||||
|
|
||||||
this.allowSplit = json.allowSplit ?? false;
|
this.allowSplit = json.allowSplit ?? false;
|
||||||
this.name = Translations.T(json.name, context + ".name");
|
this.name = Translations.T(json.name, translationContext + ".name");
|
||||||
this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`)))
|
this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`)))
|
||||||
|
|
||||||
if (json.description !== undefined) {
|
if (json.description !== undefined) {
|
||||||
|
@ -136,7 +137,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
|
|
||||||
this.description = Translations.T(
|
this.description = Translations.T(
|
||||||
json.description,
|
json.description,
|
||||||
context + ".description"
|
translationContext + ".description"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,9 +212,9 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: PresetConfig = {
|
const config: PresetConfig = {
|
||||||
title: Translations.T(pr.title, `${context}.presets[${i}].title`),
|
title: Translations.T(pr.title, `${translationContext}.presets.${i}.title`),
|
||||||
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
|
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
|
||||||
description: Translations.T(pr.description, `${context}.presets[${i}].description`),
|
description: Translations.T(pr.description, `${translationContext}.presets.${i}.description`),
|
||||||
preciseInput: preciseInput,
|
preciseInput: preciseInput,
|
||||||
exampleImages: pr.exampleImages
|
exampleImages: pr.exampleImages
|
||||||
}
|
}
|
||||||
|
@ -258,7 +259,7 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
this.filters = []
|
this.filters = []
|
||||||
} else {
|
} else {
|
||||||
this.filters = (<FilterConfigJson[]>json.filter ?? []).map((option, i) => {
|
this.filters = (<FilterConfigJson[]>json.filter ?? []).map((option, i) => {
|
||||||
return new FilterConfig(option, `${context}.filter-[${i}]`)
|
return new FilterConfig(option, `layers:${this.id}.filter.${i}`)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,11 @@ export default class LayoutConfig {
|
||||||
throw "The id of a theme should match [a-z0-9-_]*: " + json.id
|
throw "The id of a theme should match [a-z0-9-_]*: " + json.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context = (context ?? "") + "." + this.id;
|
if(context === undefined){
|
||||||
|
context = this.id
|
||||||
|
}else{
|
||||||
|
context = context + "." + this.id;
|
||||||
|
}
|
||||||
this.maintainer = json.maintainer;
|
this.maintainer = json.maintainer;
|
||||||
this.credits = json.credits;
|
this.credits = json.credits;
|
||||||
this.version = json.version;
|
this.version = json.version;
|
||||||
|
@ -99,10 +103,10 @@ export default class LayoutConfig {
|
||||||
throw "Got undefined layers for " + json.id + " at " + context
|
throw "Got undefined layers for " + json.id + " at " + context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.title = new Translation(json.title, context + ".title");
|
this.title = new Translation(json.title, "themes:"+context + ".title");
|
||||||
this.description = new Translation(json.description, context + ".description");
|
this.description = new Translation(json.description, "themes:"+context + ".description");
|
||||||
this.shortDescription = json.shortDescription === undefined ? this.description.FirstSentence() : new Translation(json.shortDescription, context + ".shortdescription");
|
this.shortDescription = json.shortDescription === undefined ? this.description.FirstSentence() : new Translation(json.shortDescription, "themes:"+context + ".shortdescription");
|
||||||
this.descriptionTail = json.descriptionTail === undefined ? undefined : new Translation(json.descriptionTail, context + ".descriptionTail");
|
this.descriptionTail = json.descriptionTail === undefined ? undefined : new Translation(json.descriptionTail, "themes:"+context + ".descriptionTail");
|
||||||
this.icon = json.icon;
|
this.icon = json.icon;
|
||||||
this.socialImage = json.socialImage ?? LayoutConfig.defaultSocialImage;
|
this.socialImage = json.socialImage ?? LayoutConfig.defaultSocialImage;
|
||||||
if (this.socialImage === "") {
|
if (this.socialImage === "") {
|
||||||
|
@ -125,7 +129,7 @@ export default class LayoutConfig {
|
||||||
href: "https://mapcomplete.osm.be/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}",
|
href: "https://mapcomplete.osm.be/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}",
|
||||||
newTab: true,
|
newTab: true,
|
||||||
requirements: ["iframe","no-welcome-message"]
|
requirements: ["iframe","no-welcome-message"]
|
||||||
}, context)
|
}, context+".extraLink")
|
||||||
|
|
||||||
|
|
||||||
this.clustering = {
|
this.clustering = {
|
||||||
|
|
|
@ -54,7 +54,6 @@ export default class TagRenderingConfig {
|
||||||
if (json === undefined) {
|
if (json === undefined) {
|
||||||
throw "Initing a TagRenderingConfig with undefined in " + context;
|
throw "Initing a TagRenderingConfig with undefined in " + context;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json === "questions") {
|
if (json === "questions") {
|
||||||
// Very special value
|
// Very special value
|
||||||
this.render = null;
|
this.render = null;
|
||||||
|
@ -70,9 +69,23 @@ export default class TagRenderingConfig {
|
||||||
json = "" + json
|
json = "" + json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let translationKey = context;
|
||||||
|
if(json["id"] !== undefined){
|
||||||
|
const layerId = context.split(".")[0]
|
||||||
|
if(json["source"]){
|
||||||
|
let src = json["source"]+":"
|
||||||
|
if(json["source"] === "shared-questions"){
|
||||||
|
src += "shared_questions."
|
||||||
|
}
|
||||||
|
translationKey = `${src}${json["id"] ?? ""}`
|
||||||
|
}else{
|
||||||
|
translationKey = `layers:${layerId}.tagRenderings.${json["id"] ?? ""}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (typeof json === "string") {
|
if (typeof json === "string") {
|
||||||
this.render = Translations.T(json, context + ".render");
|
this.render = Translations.T(json, translationKey + ".render");
|
||||||
this.multiAnswer = false;
|
this.multiAnswer = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -86,8 +99,8 @@ export default class TagRenderingConfig {
|
||||||
|
|
||||||
this.group = json.group ?? "";
|
this.group = json.group ?? "";
|
||||||
this.labels = json.labels ?? []
|
this.labels = json.labels ?? []
|
||||||
this.render = Translations.T(json.render, context + ".render");
|
this.render = Translations.T(json.render, translationKey + ".render");
|
||||||
this.question = Translations.T(json.question, context + ".question");
|
this.question = Translations.T(json.question, translationKey + ".question");
|
||||||
this.condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`);
|
this.condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`);
|
||||||
if (json.freeform) {
|
if (json.freeform) {
|
||||||
|
|
||||||
|
@ -101,7 +114,7 @@ export default class TagRenderingConfig {
|
||||||
const typeDescription = Translations.t.validation[type]?.description
|
const typeDescription = Translations.t.validation[type]?.description
|
||||||
placeholder = Translations.T(json.freeform.key+" ("+type+")")
|
placeholder = Translations.T(json.freeform.key+" ("+type+")")
|
||||||
if(typeDescription !== undefined){
|
if(typeDescription !== undefined){
|
||||||
placeholder = placeholder.Fuse(typeDescription, type)
|
placeholder = placeholder.Subs({[type]: typeDescription})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +168,7 @@ export default class TagRenderingConfig {
|
||||||
|
|
||||||
this.mappings = json.mappings.map((mapping, i) => {
|
this.mappings = json.mappings.map((mapping, i) => {
|
||||||
|
|
||||||
const ctx = `${context}.mapping[${i}]`
|
const ctx = `${translationKey}.mappings.${i}`
|
||||||
if (mapping.then === undefined) {
|
if (mapping.then === undefined) {
|
||||||
throw `${ctx}: Invalid mapping: if without body`
|
throw `${ctx}: Invalid mapping: if without body`
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default class Link extends BaseUIElement {
|
||||||
if (this._embeddedShow === undefined) {
|
if (this._embeddedShow === undefined) {
|
||||||
throw "Error: got a link where embeddedShow is undefined"
|
throw "Error: got a link where embeddedShow is undefined"
|
||||||
}
|
}
|
||||||
|
this.onClick(() => {})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
UI/Base/LinkToWeblate.ts
Normal file
33
UI/Base/LinkToWeblate.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {VariableUiElement} from "./VariableUIElement";
|
||||||
|
import Locale from "../i18n/Locale";
|
||||||
|
import Link from "./Link";
|
||||||
|
import Svg from "../../Svg";
|
||||||
|
|
||||||
|
export default class LinkToWeblate extends VariableUiElement {
|
||||||
|
constructor(context: string, availableTranslations: object) {
|
||||||
|
super( Locale.language.map(ln => {
|
||||||
|
if (Locale.showLinkToWeblate.data === false) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if(availableTranslations["*"] !== undefined){
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const icon = Svg.translate_svg()
|
||||||
|
.SetClass("rounded-full border border-gray-400 inline-block w-4 h-4 m-1 weblate-link self-center")
|
||||||
|
if(availableTranslations[ln] === undefined){
|
||||||
|
icon.SetClass("bg-red-400")
|
||||||
|
}
|
||||||
|
return new Link(icon,
|
||||||
|
LinkToWeblate.hrefToWeblate(ln, context), true)
|
||||||
|
} ,[Locale.showLinkToWeblate]));
|
||||||
|
this.SetClass("enable-links hidden-on-mobile")
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hrefToWeblate(language: string, contextKey: string): string{
|
||||||
|
const [category, ...rest] = contextKey.split(":")
|
||||||
|
const key = rest.join(":")
|
||||||
|
|
||||||
|
const baseUrl = "https://hosted.weblate.org/translate/mapcomplete/"
|
||||||
|
return baseUrl + category + "/" + language + "/?offset=1&q=context%3A%3D%22" + key + "%22"
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import Constants from "../../Models/Constants";
|
||||||
import ContributorCount from "../../Logic/ContributorCount";
|
import ContributorCount from "../../Logic/ContributorCount";
|
||||||
import Img from "../Base/Img";
|
import Img from "../Base/Img";
|
||||||
import {Translation} from "../i18n/Translation";
|
import {Translation} from "../i18n/Translation";
|
||||||
|
import TranslatorsPanel from "./TranslatorsPanel";
|
||||||
|
|
||||||
export class OpenIdEditor extends VariableUiElement {
|
export class OpenIdEditor extends VariableUiElement {
|
||||||
constructor(state: { locationControl: UIEventSource<Loc> }, iconStyle?: string, objectId?: string) {
|
constructor(state: { locationControl: UIEventSource<Loc> }, iconStyle?: string, objectId?: string) {
|
||||||
|
@ -110,7 +111,8 @@ export default class CopyrightPanel extends Combine {
|
||||||
featurePipeline: FeaturePipeline,
|
featurePipeline: FeaturePipeline,
|
||||||
currentBounds: UIEventSource<BBox>,
|
currentBounds: UIEventSource<BBox>,
|
||||||
locationControl: UIEventSource<Loc>,
|
locationControl: UIEventSource<Loc>,
|
||||||
osmConnection: OsmConnection
|
osmConnection: OsmConnection,
|
||||||
|
isTranslator: UIEventSource<boolean>
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const t = Translations.t.general.attribution
|
const t = Translations.t.general.attribution
|
||||||
|
@ -131,25 +133,21 @@ export default class CopyrightPanel extends Combine {
|
||||||
}),
|
}),
|
||||||
new OpenIdEditor(state, iconStyle),
|
new OpenIdEditor(state, iconStyle),
|
||||||
new OpenMapillary(state, iconStyle),
|
new OpenMapillary(state, iconStyle),
|
||||||
new OpenJosm(state, iconStyle)
|
new OpenJosm(state, iconStyle),
|
||||||
|
new TranslatorsPanel(state, iconStyle)
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const iconAttributions = layoutToUse.usedImages.map(CopyrightPanel.IconAttribution)
|
const iconAttributions = layoutToUse.usedImages.map(CopyrightPanel.IconAttribution)
|
||||||
|
|
||||||
let maintainer: BaseUIElement = undefined
|
let maintainer: BaseUIElement = undefined
|
||||||
if (layoutToUse.maintainer !== undefined && layoutToUse.maintainer !== "" && layoutToUse.maintainer.toLowerCase() !== "mapcomplete") {
|
if (layoutToUse.maintainer !== undefined && layoutToUse.maintainer !== "" && layoutToUse.maintainer.toLowerCase() !== "mapcomplete") {
|
||||||
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
|
maintainer = t.themeBy.Subs({author: layoutToUse.maintainer})
|
||||||
}
|
}
|
||||||
|
|
||||||
const contributions = new ContributorCount(state).Contributors
|
const contributions = new ContributorCount(state).Contributors
|
||||||
|
|
||||||
super([
|
const dataContributors = new VariableUiElement(contributions.map(contributions => {
|
||||||
Translations.t.general.attribution.attributionContent,
|
|
||||||
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
|
|
||||||
maintainer,
|
|
||||||
new Combine(actionButtons).SetClass("block w-full"),
|
|
||||||
new FixedUiElement(layoutToUse.credits),
|
|
||||||
new VariableUiElement(contributions.map(contributions => {
|
|
||||||
if (contributions === undefined) {
|
if (contributions === undefined) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -170,20 +168,29 @@ export default class CopyrightPanel extends Combine {
|
||||||
const contribs = links.join(", ")
|
const contribs = links.join(", ")
|
||||||
|
|
||||||
if (hiddenCount <= 0) {
|
if (hiddenCount <= 0) {
|
||||||
return Translations.t.general.attribution.mapContributionsBy.Subs({
|
return t.mapContributionsBy.Subs({
|
||||||
contributors: contribs
|
contributors: contribs
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Translations.t.general.attribution.mapContributionsByAndHidden.Subs({
|
return t.mapContributionsByAndHidden.Subs({
|
||||||
contributors: contribs,
|
contributors: contribs,
|
||||||
hiddenCount: hiddenCount
|
hiddenCount: hiddenCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})),
|
}))
|
||||||
CopyrightPanel.CodeContributors(contributors, Translations.t.general.attribution.codeContributionsBy),
|
|
||||||
CopyrightPanel.CodeContributors(translators, Translations.t.general.attribution.translatedBy),
|
super([
|
||||||
|
new Title(t.attributionTitle),
|
||||||
|
t.attributionContent,
|
||||||
|
maintainer,
|
||||||
|
new FixedUiElement(layoutToUse.credits),
|
||||||
|
dataContributors,
|
||||||
|
CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy),
|
||||||
|
CopyrightPanel.CodeContributors(translators, t.translatedBy),
|
||||||
|
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
|
||||||
|
new Combine(actionButtons).SetClass("block w-full"),
|
||||||
new Title(t.iconAttribution.title, 3),
|
new Title(t.iconAttribution.title, 3),
|
||||||
...iconAttributions
|
...iconAttributions
|
||||||
].map(e => e?.SetClass("mt-4")));
|
].map(e => e?.SetClass("mt-4")));
|
||||||
|
@ -213,9 +220,9 @@ export default class CopyrightPanel extends Combine {
|
||||||
|
|
||||||
private static IconAttribution(iconPath: string): BaseUIElement {
|
private static IconAttribution(iconPath: string): BaseUIElement {
|
||||||
if (iconPath.startsWith("http")) {
|
if (iconPath.startsWith("http")) {
|
||||||
try{
|
try {
|
||||||
iconPath = "." + new URL(iconPath).pathname;
|
iconPath = "." + new URL(iconPath).pathname;
|
||||||
}catch(e){
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +242,7 @@ export default class CopyrightPanel extends Combine {
|
||||||
new Combine([
|
new Combine([
|
||||||
new FixedUiElement(license.authors.join("; ")).SetClass("font-bold"),
|
new FixedUiElement(license.authors.join("; ")).SetClass("font-bold"),
|
||||||
license.license,
|
license.license,
|
||||||
new Combine([ ...sources.map(lnk => {
|
new Combine([...sources.map(lnk => {
|
||||||
let sourceLinkContent = lnk;
|
let sourceLinkContent = lnk;
|
||||||
try {
|
try {
|
||||||
sourceLinkContent = new URL(lnk).hostname
|
sourceLinkContent = new URL(lnk).hostname
|
||||||
|
|
|
@ -101,9 +101,7 @@ export default class FilterView extends VariableUiElement {
|
||||||
iconStyle
|
iconStyle
|
||||||
);
|
);
|
||||||
|
|
||||||
const name: Translation = Translations.WT(
|
const name: Translation = filteredLayer.layerDef.name.Clone()
|
||||||
filteredLayer.layerDef.name
|
|
||||||
);
|
|
||||||
|
|
||||||
const styledNameChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-3");
|
const styledNameChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-3");
|
||||||
|
|
||||||
|
|
|
@ -83,9 +83,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||||
new Combine(
|
new Combine(
|
||||||
[
|
[
|
||||||
Translations.t.general.openStreetMapIntro.SetClass("link-underline"),
|
Translations.t.general.openStreetMapIntro.SetClass("link-underline"),
|
||||||
Translations.t.general.attribution.attributionTitle,
|
|
||||||
new CopyrightPanel(state)
|
new CopyrightPanel(state)
|
||||||
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import BaseUIElement from "../BaseUIElement";
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Loc from "../../Models/Loc";
|
import Loc from "../../Models/Loc";
|
||||||
import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||||
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
@ -53,7 +53,8 @@ export default class MoreScreen extends Combine {
|
||||||
icon: string,
|
icon: string,
|
||||||
title: any,
|
title: any,
|
||||||
shortDescription: any,
|
shortDescription: any,
|
||||||
definition?: any
|
definition?: any,
|
||||||
|
mustHaveLanguage?: boolean
|
||||||
}, isCustom: boolean = false
|
}, isCustom: boolean = false
|
||||||
):
|
):
|
||||||
BaseUIElement {
|
BaseUIElement {
|
||||||
|
@ -109,7 +110,7 @@ export default class MoreScreen extends Combine {
|
||||||
return new SubtleButton(layout.icon,
|
return new SubtleButton(layout.icon,
|
||||||
new Combine([
|
new Combine([
|
||||||
`<dt class='text-lg leading-6 font-medium text-gray-900 group-hover:text-blue-800'>`,
|
`<dt class='text-lg leading-6 font-medium text-gray-900 group-hover:text-blue-800'>`,
|
||||||
new Translation(layout.title),
|
new Translation(layout.title, !isCustom && !layout.mustHaveLanguage ? "themes:"+layout.id+".title" : undefined),
|
||||||
`</dt>`,
|
`</dt>`,
|
||||||
`<dd class='mt-1 text-base text-gray-500 group-hover:text-blue-900 overflow-ellipsis'>`,
|
`<dd class='mt-1 text-base text-gray-500 group-hover:text-blue-900 overflow-ellipsis'>`,
|
||||||
new Translation(layout.shortDescription)?.SetClass("subtle") ?? "",
|
new Translation(layout.shortDescription)?.SetClass("subtle") ?? "",
|
||||||
|
@ -142,9 +143,10 @@ export default class MoreScreen extends Combine {
|
||||||
icon: string,
|
icon: string,
|
||||||
title: any,
|
title: any,
|
||||||
shortDescription: any,
|
shortDescription: any,
|
||||||
definition?: any
|
definition?: any,
|
||||||
|
isOfficial: boolean
|
||||||
} = JSON.parse(str)
|
} = JSON.parse(str)
|
||||||
|
value.isOfficial = false
|
||||||
return MoreScreen.createLinkButton(state, value, true)
|
return MoreScreen.createLinkButton(state, value, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.debug("Could not parse unofficial theme information for " + id, "The json is: ", str, e)
|
console.debug("Could not parse unofficial theme information for " + id, "The json is: ", str, e)
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default class SimpleAddUI extends Toggle {
|
||||||
selectedPreset.setData(undefined)
|
selectedPreset.setData(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = Translations.t.general.add.addNew.Subs({category: preset.name});
|
const message = Translations.t.general.add.addNew.Subs({category: preset.name}, preset.name["context"]);
|
||||||
return new ConfirmLocationOfPoint(state, filterViewIsOpened, preset,
|
return new ConfirmLocationOfPoint(state, filterViewIsOpened, preset,
|
||||||
message,
|
message,
|
||||||
state.LastClickLocation.data,
|
state.LastClickLocation.data,
|
||||||
|
@ -184,12 +184,13 @@ export default class SimpleAddUI extends Toggle {
|
||||||
|
|
||||||
private static CreatePresetSelectButton(preset: PresetInfo) {
|
private static CreatePresetSelectButton(preset: PresetInfo) {
|
||||||
|
|
||||||
|
const title = Translations.t.general.add.addNew.Subs({
|
||||||
|
category: preset.name
|
||||||
|
}, preset.name["context"])
|
||||||
return new SubtleButton(
|
return new SubtleButton(
|
||||||
preset.icon(),
|
preset.icon(),
|
||||||
new Combine([
|
new Combine([
|
||||||
Translations.t.general.add.addNew.Subs({
|
title.SetClass("font-bold"),
|
||||||
category: preset.name
|
|
||||||
}).SetClass("font-bold"),
|
|
||||||
Translations.WT(preset.description)?.FirstSentence()
|
Translations.WT(preset.description)?.FirstSentence()
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col")
|
||||||
)
|
)
|
||||||
|
|
124
UI/BigComponents/TranslatorsPanel.ts
Normal file
124
UI/BigComponents/TranslatorsPanel.ts
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
import Lazy from "../Base/Lazy";
|
||||||
|
import {Utils} from "../../Utils";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
import Locale from "../i18n/Locale";
|
||||||
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||||
|
import {Translation} from "../i18n/Translation";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import Link from "../Base/Link";
|
||||||
|
import LinkToWeblate from "../Base/LinkToWeblate";
|
||||||
|
import Toggleable from "../Base/Toggleable";
|
||||||
|
import Title from "../Base/Title";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {SubtleButton} from "../Base/SubtleButton";
|
||||||
|
import Svg from "../../Svg";
|
||||||
|
|
||||||
|
|
||||||
|
class TranslatorsPanelContent extends Combine {
|
||||||
|
constructor(layout: LayoutConfig, isTranslator: UIEventSource<boolean>) {
|
||||||
|
const t = Translations.t.translations
|
||||||
|
const completeness = new Map<string, number>()
|
||||||
|
let total = 0
|
||||||
|
const untranslated = new Map<string, string[]>()
|
||||||
|
Utils.WalkObject(layout, (o, path) => {
|
||||||
|
const translation = <Translation><any>o;
|
||||||
|
for (const lang of translation.SupportedLanguages()) {
|
||||||
|
completeness.set(lang, 1 + (completeness.get(lang) ?? 0))
|
||||||
|
}
|
||||||
|
layout.title.SupportedLanguages().forEach(ln => {
|
||||||
|
const trans = translation.translations
|
||||||
|
if (trans["*"] !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trans[ln] === undefined) {
|
||||||
|
if (!untranslated.has(ln)) {
|
||||||
|
untranslated.set(ln, [])
|
||||||
|
}
|
||||||
|
untranslated.get(ln).push(translation.context)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(translation.translations["*"] === undefined){
|
||||||
|
total++
|
||||||
|
}
|
||||||
|
}, o => {
|
||||||
|
if (o === undefined || o === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return o instanceof Translation;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const seed = t.completeness
|
||||||
|
for (const ln of Array.from(completeness.keys())) {
|
||||||
|
if(ln === "*"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (seed.translations[ln] === undefined) {
|
||||||
|
seed.translations[ln] = seed.translations["en"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const completenessTr = {}
|
||||||
|
const completenessPercentage = {}
|
||||||
|
seed.SupportedLanguages().forEach(ln => {
|
||||||
|
completenessTr[ln] = ""+(completeness.get(ln) ?? 0)
|
||||||
|
completenessPercentage[ln] = ""+Math.round(100 * (completeness.get(ln) ?? 0) / total)
|
||||||
|
})
|
||||||
|
|
||||||
|
// "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}",
|
||||||
|
const translated = seed.Subs({total, theme: layout.title,
|
||||||
|
percentage: new Translation(completenessPercentage),
|
||||||
|
translated: new Translation(completenessTr)
|
||||||
|
})
|
||||||
|
|
||||||
|
const missingTranslationsFor = (ln: string) => Utils.NoNull(untranslated.get(ln) ?? [])
|
||||||
|
.filter(ctx => ctx.indexOf(':') > 0)
|
||||||
|
.map(ctx => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import"))
|
||||||
|
.map(context => new Link(context, LinkToWeblate.hrefToWeblate(ln, context), true))
|
||||||
|
|
||||||
|
const disable = new SubtleButton(undefined, t.deactivate)
|
||||||
|
.onClick(() => {
|
||||||
|
Locale.showLinkToWeblate.setData(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
super([
|
||||||
|
new Title(
|
||||||
|
Translations.t.translations.activateButton,
|
||||||
|
),
|
||||||
|
new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator),
|
||||||
|
t.help,
|
||||||
|
translated,
|
||||||
|
disable,
|
||||||
|
new VariableUiElement(Locale.language.map(ln => {
|
||||||
|
|
||||||
|
const missing = missingTranslationsFor(ln)
|
||||||
|
if (missing.length === 0) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return new Toggleable(
|
||||||
|
new Title(Translations.t.translations.missing.Subs({count: missing.length})),
|
||||||
|
new Combine(missing).SetClass("flex flex-col")
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TranslatorsPanel extends Toggle {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(state: { layoutToUse: LayoutConfig, isTranslator: UIEventSource<boolean> }, iconStyle?: string) {
|
||||||
|
const t = Translations.t.translations
|
||||||
|
super(
|
||||||
|
new Lazy(() => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator)
|
||||||
|
).SetClass("flex flex-col"),
|
||||||
|
new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() => Locale.showLinkToWeblate.setData(true)),
|
||||||
|
Locale.showLinkToWeblate
|
||||||
|
)
|
||||||
|
this.SetClass("hidden-on-mobile")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -842,7 +842,7 @@ export default class SpecialVisualizations {
|
||||||
|
|
||||||
return new LoginToggle(
|
return new LoginToggle(
|
||||||
new Combine([
|
new Combine([
|
||||||
new Title("Add a comment"),
|
new Title(t.addAComment),
|
||||||
textField,
|
textField,
|
||||||
new Combine([
|
new Combine([
|
||||||
stateButtons.SetClass("sm:mr-2"),
|
stateButtons.SetClass("sm:mr-2"),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Combine from "./Base/Combine";
|
||||||
import BaseUIElement from "./BaseUIElement";
|
import BaseUIElement from "./BaseUIElement";
|
||||||
import {DefaultGuiState} from "./DefaultGuiState";
|
import {DefaultGuiState} from "./DefaultGuiState";
|
||||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
||||||
|
import LinkToWeblate from "./Base/LinkToWeblate";
|
||||||
|
|
||||||
export class SubstitutedTranslation extends VariableUiElement {
|
export class SubstitutedTranslation extends VariableUiElement {
|
||||||
|
|
||||||
|
@ -34,6 +35,8 @@ export class SubstitutedTranslation extends VariableUiElement {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const linkToWeblate = new LinkToWeblate(translation.context, translation.translations)
|
||||||
|
|
||||||
super(
|
super(
|
||||||
Locale.language.map(language => {
|
Locale.language.map(language => {
|
||||||
let txt = translation?.textFor(language);
|
let txt = translation?.textFor(language);
|
||||||
|
@ -44,7 +47,7 @@ export class SubstitutedTranslation extends VariableUiElement {
|
||||||
txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`)
|
txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
|
const allElements = SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
|
||||||
proto => {
|
proto => {
|
||||||
if (proto.fixed !== undefined) {
|
if (proto.fixed !== undefined) {
|
||||||
return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
|
return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
|
||||||
|
@ -56,8 +59,12 @@ export class SubstitutedTranslation extends VariableUiElement {
|
||||||
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
|
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
|
||||||
return new FixedUiElement(`Could not generate special rendering for ${viz.func.funcName}(${viz.args.join(", ")}) ${e}`).SetStyle("alert")
|
return new FixedUiElement(`Could not generate special rendering for ${viz.func.funcName}(${viz.args.join(", ")}) ${e}`).SetStyle("alert")
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
))
|
allElements.push(linkToWeblate)
|
||||||
|
|
||||||
|
return new Combine(
|
||||||
|
allElements
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -39,16 +39,12 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
||||||
{
|
{
|
||||||
property: "P569",
|
property: "P569",
|
||||||
requires: WikidataPreviewBox.isHuman,
|
requires: WikidataPreviewBox.isHuman,
|
||||||
display: new Translation({
|
display: Translations.t.general.wikipedia.previewbox.born
|
||||||
"*": "Born: {value}"
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "P570",
|
property: "P570",
|
||||||
requires: WikidataPreviewBox.isHuman,
|
requires: WikidataPreviewBox.isHuman,
|
||||||
display: new Translation({
|
display:Translations.t.general.wikipedia.previewbox.died
|
||||||
"*": "Died: {value}"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {QueryParameters} from "../../Logic/Web/QueryParameters";
|
||||||
export default class Locale {
|
export default class Locale {
|
||||||
|
|
||||||
public static language: UIEventSource<string> = Locale.setup();
|
public static language: UIEventSource<string> = Locale.setup();
|
||||||
|
public static showLinkToWeblate: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
private static setup() {
|
private static setup() {
|
||||||
const source = LocalStorageSource.Get('language', "en");
|
const source = LocalStorageSource.Get('language', "en");
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import Locale from "./Locale";
|
import Locale from "./Locale";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Link from "../Base/Link";
|
||||||
|
import Svg from "../../Svg";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import LinkToWeblate from "../Base/LinkToWeblate";
|
||||||
|
|
||||||
export class Translation extends BaseUIElement {
|
export class Translation extends BaseUIElement {
|
||||||
|
|
||||||
public static forcedLanguage = undefined;
|
public static forcedLanguage = undefined;
|
||||||
|
|
||||||
public readonly translations: object
|
public readonly translations: object
|
||||||
|
context?: string;
|
||||||
|
|
||||||
constructor(translations: object, context?: string) {
|
constructor(translations: object, context?: string) {
|
||||||
super()
|
super()
|
||||||
|
this.context = context;
|
||||||
if (translations === undefined) {
|
if (translations === undefined) {
|
||||||
console.error("Translation without content at "+context)
|
console.error("Translation without content at "+context)
|
||||||
throw `Translation without content (${context})`
|
throw `Translation without content (${context})`
|
||||||
|
@ -101,15 +107,37 @@ export class Translation extends BaseUIElement {
|
||||||
InnerConstructElement(): HTMLElement {
|
InnerConstructElement(): HTMLElement {
|
||||||
const el = document.createElement("span")
|
const el = document.createElement("span")
|
||||||
const self = this
|
const self = this
|
||||||
|
|
||||||
|
|
||||||
Locale.language.addCallbackAndRun(_ => {
|
Locale.language.addCallbackAndRun(_ => {
|
||||||
if (self.isDestroyed) {
|
if (self.isDestroyed) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
el.innerHTML = this.txt
|
el.innerHTML = this.txt
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (self.translations["*"] !== undefined || self.context === undefined || self.context?.indexOf(":") < 0) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const linkToWeblate = new LinkToWeblate(self.context, self.translations)
|
||||||
|
|
||||||
|
const wrapper = document.createElement("span")
|
||||||
|
wrapper.appendChild(el)
|
||||||
|
wrapper.classList.add("flex")
|
||||||
|
Locale.showLinkToWeblate.addCallbackAndRun(doShow => {
|
||||||
|
|
||||||
|
if (!doShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapper.appendChild(linkToWeblate.ConstructElement())
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return wrapper ;
|
||||||
|
}
|
||||||
|
|
||||||
public SupportedLanguages(): string[] {
|
public SupportedLanguages(): string[] {
|
||||||
const langs = []
|
const langs = []
|
||||||
for (const translationsKey in this.translations) {
|
for (const translationsKey in this.translations) {
|
||||||
|
@ -131,11 +159,25 @@ export class Translation extends BaseUIElement {
|
||||||
return this.SupportedLanguages().map(lng => this.translations[lng]);
|
return this.SupportedLanguages().map(lng => this.translations[lng]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Subs(text: any): Translation {
|
/**
|
||||||
return this.OnEveryLanguage((template, lang) => Utils.SubstituteKeys(template, text, lang))
|
* Substitutes text in a translation.
|
||||||
|
* If a translation is passed, it'll be fused
|
||||||
|
*
|
||||||
|
* // Should replace simple keys
|
||||||
|
* new Translation({"en": "Some text {key}"}).Subs({key: "xyz"}).textFor("en") // => "Some text xyz"
|
||||||
|
*
|
||||||
|
* // Should fuse translations
|
||||||
|
* const subpart = new Translation({"en": "subpart","nl":"onderdeel"})
|
||||||
|
* const tr = new Translation({"en": "Full sentence with {part}", nl: "Volledige zin met {part}"})
|
||||||
|
* const subbed = tr.Subs({part: subpart})
|
||||||
|
* subbed.textFor("en") // => "Full sentence with subpart"
|
||||||
|
* subbed.textFor("nl") // => "Volledige zin met onderdeel"
|
||||||
|
*/
|
||||||
|
public Subs(text: any, context?: string): Translation {
|
||||||
|
return this.OnEveryLanguage((template, lang) => Utils.SubstituteKeys(template, text, lang), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
public OnEveryLanguage(f: (s: string, language: string) => string): Translation {
|
public OnEveryLanguage(f: (s: string, language: string) => string, context?: string): Translation {
|
||||||
const newTranslations = {};
|
const newTranslations = {};
|
||||||
for (const lang in this.translations) {
|
for (const lang in this.translations) {
|
||||||
if (!this.translations.hasOwnProperty(lang)) {
|
if (!this.translations.hasOwnProperty(lang)) {
|
||||||
|
@ -143,37 +185,10 @@ export class Translation extends BaseUIElement {
|
||||||
}
|
}
|
||||||
newTranslations[lang] = f(this.translations[lang], lang);
|
newTranslations[lang] = f(this.translations[lang], lang);
|
||||||
}
|
}
|
||||||
return new Translation(newTranslations);
|
return new Translation(newTranslations, context ?? this.context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Given a translation such as `{en: "How much of bicycle_types are rented here}` (which is this translation)
|
|
||||||
* and a translation object `{ en: "electrical bikes" }`, plus the translation specification `bicycle_types`, will return
|
|
||||||
* a new translation:
|
|
||||||
* `{en: "How much electrical bikes are rented here?"}`
|
|
||||||
*
|
|
||||||
* @param translationObject
|
|
||||||
* @param stringToReplace
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
public Fuse(translationObject: Translation, stringToReplace: string): Translation{
|
|
||||||
const translations = this.translations
|
|
||||||
const newTranslations = {}
|
|
||||||
for (const lang in translations) {
|
|
||||||
const target = translationObject.textFor(lang)
|
|
||||||
if(target === undefined){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if(typeof target !== "string"){
|
|
||||||
throw "Invalid object in Translation.fuse: translationObject['"+lang+"'] is not a string, it is: "+JSON.stringify(target)
|
|
||||||
}
|
|
||||||
newTranslations[lang] = this.translations[lang].replaceAll(stringToReplace, target)
|
|
||||||
}
|
|
||||||
return new Translation(newTranslations)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the given string with the given text in the language.
|
* Replaces the given string with the given text in the language.
|
||||||
* Other substitutions are left in place
|
* Other substitutions are left in place
|
||||||
|
@ -190,7 +205,7 @@ export class Translation extends BaseUIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Clone() {
|
public Clone() {
|
||||||
return new Translation(this.translations)
|
return new Translation(this.translations, this.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
FirstSentence() {
|
FirstSentence() {
|
||||||
|
@ -256,4 +271,6 @@ export class Translation extends BaseUIElement {
|
||||||
AsMarkdown(): string {
|
AsMarkdown(): string {
|
||||||
return this.txt
|
return this.txt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
31
Utils.ts
31
Utils.ts
|
@ -513,6 +513,37 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
return cp
|
return cp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks an object recursively. Will hang on objects with loops
|
||||||
|
*/
|
||||||
|
static WalkObject(json: any, collect: (v: number | string | boolean | undefined, path: string[]) => any, isLeaf: (object) => boolean = undefined, path = []) {
|
||||||
|
if (json === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const jtp = typeof json
|
||||||
|
if (isLeaf !== undefined) {
|
||||||
|
if (jtp !== "object") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLeaf(json)) {
|
||||||
|
return collect(json, path)
|
||||||
|
}
|
||||||
|
} else if (jtp === "boolean" || jtp === "string" || jtp === "number") {
|
||||||
|
return collect(json,path)
|
||||||
|
}
|
||||||
|
if (Array.isArray(json)) {
|
||||||
|
return json.map((sub,i) => {
|
||||||
|
return Utils.WalkObject(sub, collect, isLeaf,[...path, i]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in json) {
|
||||||
|
Utils.WalkObject(json[key], collect, isLeaf, [...path,key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static getOrSetDefault<K, V>(dict: Map<K, V>, k: K, v: () => V) {
|
static getOrSetDefault<K, V>(dict: Map<K, V>, k: K, v: () => V) {
|
||||||
let found = dict.get(k);
|
let found = dict.get(k);
|
||||||
if (found !== undefined) {
|
if (found !== undefined) {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"contributors":[{"commits":3145,"contributor":"Pieter Vander Vennet"},{"commits":64,"contributor":"Robin van der Linde"},{"commits":38,"contributor":"Tobias"},{"commits":33,"contributor":"Christian Neumann"},{"commits":31,"contributor":"Win Olario"},{"commits":31,"contributor":"Pieter Fiers"},{"commits":26,"contributor":"karelleketers"},{"commits":24,"contributor":"Ward"},{"commits":20,"contributor":"Joost"},{"commits":19,"contributor":"Sebastian Kürten"},{"commits":18,"contributor":"Arno Deceuninck"},{"commits":17,"contributor":"pgm-chardelv1"},{"commits":16,"contributor":"Hosted Weblate"},{"commits":15,"contributor":"ToastHawaii"},{"commits":13,"contributor":"riQQ"},{"commits":13,"contributor":"Nicole"},{"commits":12,"contributor":"Tobias Jordans"},{"commits":12,"contributor":"Bavo Vanderghote"},{"commits":10,"contributor":"LiamSimons"},{"commits":8,"contributor":"dependabot[bot]"},{"commits":8,"contributor":"Midgard"},{"commits":7,"contributor":"RobJN"},{"commits":7,"contributor":"Mateusz Konieczny"},{"commits":7,"contributor":"Flo Edelmann"},{"commits":7,"contributor":"Binnette"},{"commits":7,"contributor":"yopaseopor"},{"commits":6,"contributor":"pelderson"},{"commits":5,"contributor":"David Haberthür"},{"commits":4,"contributor":"Ward Beyens"},{"commits":3,"contributor":"Léo Villeveygoux"},{"commits":2,"contributor":"arrival-spring"},{"commits":2,"contributor":"Strubbl"},{"commits":2,"contributor":"RayBB"},{"commits":2,"contributor":"Charlotte Delvaux"},{"commits":2,"contributor":"Supaplex"},{"commits":2,"contributor":"pbarban"},{"commits":2,"contributor":"graveelius"},{"commits":2,"contributor":"Stanislas Gueniffey"},{"commits":1,"contributor":"Jiří Podhorecký"},{"commits":1,"contributor":"Mark Rogerson"},{"commits":1,"contributor":"nicole_s"},{"commits":1,"contributor":"SC"},{"commits":1,"contributor":"Raphael Das Gupta"},{"commits":1,"contributor":"Nikolay Korotkiy"},{"commits":1,"contributor":"Seppe Santens"},{"commits":1,"contributor":"root"},{"commits":1,"contributor":"Allan Nordhøy"},{"commits":1,"contributor":"快乐的老鼠宝宝"},{"commits":1,"contributor":"Sebastian"},{"commits":1,"contributor":"Hiroshi Miura"},{"commits":1,"contributor":"riiga"},{"commits":1,"contributor":"Vinicius"},{"commits":1,"contributor":"Alexey Shabanov"},{"commits":1,"contributor":"Polgár Sándor"},{"commits":1,"contributor":"SiegbjornSitumeang"},{"commits":1,"contributor":"Marco"},{"commits":1,"contributor":"mozita"},{"commits":1,"contributor":"Schouppe Joost"},{"commits":1,"contributor":"Thibault Molleman"},{"commits":1,"contributor":"Noémie"},{"commits":1,"contributor":"Tomas Fiers"},{"commits":1,"contributor":"tbowdecl97"}]}
|
{"contributors":[{"commits":3421,"contributor":"Pieter Vander Vennet"},{"commits":86,"contributor":"Robin van der Linde"},{"commits":39,"contributor":"Tobias"},{"commits":33,"contributor":"Christian Neumann"},{"commits":31,"contributor":"Win Olario"},{"commits":31,"contributor":"Pieter Fiers"},{"commits":26,"contributor":"karelleketers"},{"commits":24,"contributor":"Ward"},{"commits":20,"contributor":"Joost"},{"commits":19,"contributor":"Sebastian Kürten"},{"commits":18,"contributor":"riQQ"},{"commits":18,"contributor":"Arno Deceuninck"},{"commits":17,"contributor":"pgm-chardelv1"},{"commits":16,"contributor":"Hosted Weblate"},{"commits":15,"contributor":"ToastHawaii"},{"commits":13,"contributor":"Nicole"},{"commits":12,"contributor":"Tobias Jordans"},{"commits":12,"contributor":"Bavo Vanderghote"},{"commits":10,"contributor":"LiamSimons"},{"commits":8,"contributor":"dependabot[bot]"},{"commits":8,"contributor":"Midgard"},{"commits":7,"contributor":"RobJN"},{"commits":7,"contributor":"Mateusz Konieczny"},{"commits":7,"contributor":"Flo Edelmann"},{"commits":7,"contributor":"Binnette"},{"commits":7,"contributor":"yopaseopor"},{"commits":6,"contributor":"pelderson"},{"commits":5,"contributor":"David Haberthür"},{"commits":4,"contributor":"Ward Beyens"},{"commits":3,"contributor":"Weblate (bot)"},{"commits":3,"contributor":"Léo Villeveygoux"},{"commits":2,"contributor":"Codain"},{"commits":2,"contributor":"arrival-spring"},{"commits":2,"contributor":"Strubbl"},{"commits":2,"contributor":"RayBB"},{"commits":2,"contributor":"Charlotte Delvaux"},{"commits":2,"contributor":"Supaplex"},{"commits":2,"contributor":"pbarban"},{"commits":2,"contributor":"graveelius"},{"commits":2,"contributor":"Stanislas Gueniffey"},{"commits":1,"contributor":"Štefan Baebler"},{"commits":1,"contributor":"Jiří Podhorecký"},{"commits":1,"contributor":"Mark Rogerson"},{"commits":1,"contributor":"nicole_s"},{"commits":1,"contributor":"SC"},{"commits":1,"contributor":"Raphael Das Gupta"},{"commits":1,"contributor":"Nikolay Korotkiy"},{"commits":1,"contributor":"Seppe Santens"},{"commits":1,"contributor":"root"},{"commits":1,"contributor":"Allan Nordhøy"},{"commits":1,"contributor":"快乐的老鼠宝宝"},{"commits":1,"contributor":"Sebastian"},{"commits":1,"contributor":"Hiroshi Miura"},{"commits":1,"contributor":"riiga"},{"commits":1,"contributor":"Vinicius"},{"commits":1,"contributor":"Alexey Shabanov"},{"commits":1,"contributor":"Polgár Sándor"},{"commits":1,"contributor":"SiegbjornSitumeang"},{"commits":1,"contributor":"Marco"},{"commits":1,"contributor":"mozita"},{"commits":1,"contributor":"Schouppe Joost"},{"commits":1,"contributor":"Thibault Molleman"},{"commits":1,"contributor":"Noémie"},{"commits":1,"contributor":"Tomas Fiers"},{"commits":1,"contributor":"tbowdecl97"}]}
|
|
@ -298,18 +298,13 @@
|
||||||
},
|
},
|
||||||
"id": "bike_shop-email"
|
"id": "bike_shop-email"
|
||||||
},
|
},
|
||||||
{
|
"opening_hours",
|
||||||
"render": "{opening_hours_table(opening_hours)}",
|
|
||||||
"question": "When is this shop opened?",
|
|
||||||
"freeform": {
|
|
||||||
"key": "opening_hours",
|
|
||||||
"type": "opening_hours"
|
|
||||||
},
|
|
||||||
"id": "bike_shop-opening_hours"
|
|
||||||
},
|
|
||||||
"description",
|
"description",
|
||||||
{
|
{
|
||||||
"render": "Enkel voor {access}",
|
"render": {
|
||||||
|
"en": "Only accessible to {access}",
|
||||||
|
"nl": "Enkel voor {access}"
|
||||||
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "access"
|
"key": "access"
|
||||||
},
|
},
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "uk_addresses_import_button",
|
"id": "uk_addresses_import_button",
|
||||||
"render":{
|
"render": {
|
||||||
"special": {
|
"special": {
|
||||||
"type": "import_button",
|
"type": "import_button",
|
||||||
"targetLayer": "address",
|
"targetLayer": "address",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"contributors":[{"commits":60,"contributor":"danieldegroot2"},{"commits":41,"contributor":"kjon"},{"commits":29,"contributor":"Artem"},{"commits":23,"contributor":"Pieter Vander Vennet"},{"commits":22,"contributor":"Supaplex"},{"commits":22,"contributor":"Marco"},{"commits":22,"contributor":"Allan Nordhøy"},{"commits":21,"contributor":"Babos Gábor"},{"commits":21,"contributor":"Anonymous"},{"commits":15,"contributor":"WaldiS"},{"commits":14,"contributor":"J. Lavoie"},{"commits":13,"contributor":"SC"},{"commits":10,"contributor":"Reza Almanda"},{"commits":9,"contributor":"Jacque Fresco"},{"commits":8,"contributor":"LeJun"},{"commits":8,"contributor":"Irina"},{"commits":6,"contributor":"Nikolay Korotkiy"},{"commits":6,"contributor":"William Weber Berrutti"},{"commits":6,"contributor":"lvgx"},{"commits":5,"contributor":"Piotr"},{"commits":5,"contributor":"Robin van der Linde"},{"commits":5,"contributor":"seppesantens"},{"commits":5,"contributor":"Vinicius"},{"commits":5,"contributor":"Alexey Shabanov"},{"commits":4,"contributor":"Jeff Huang"},{"commits":4,"contributor":"Joost"},{"commits":4,"contributor":"Adolfo Jayme Barrientos"},{"commits":4,"contributor":"Polgár Sándor"},{"commits":4,"contributor":"David Haberthür"},{"commits":4,"contributor":"phlostically"},{"commits":4,"contributor":"Jan Zabel"},{"commits":4,"contributor":"Fabio Bettani"},{"commits":3,"contributor":"Sasha"},{"commits":3,"contributor":"Jose Luis Infante"},{"commits":3,"contributor":"Francois"},{"commits":3,"contributor":"Eduardo Addad de Oliveira"},{"commits":3,"contributor":"Wiktor Przybylski"},{"commits":3,"contributor":"Erik Palm"},{"commits":3,"contributor":"vankos"},{"commits":3,"contributor":"JCGF-OSM"},{"commits":3,"contributor":"Hiroshi Miura"},{"commits":3,"contributor":"SiegbjornSitumeang"},{"commits":2,"contributor":"わたなべけんご"},{"commits":2,"contributor":"Mateusz Konieczny"},{"commits":2,"contributor":"Kristoffer Grundström"},{"commits":2,"contributor":"el_libre como el chaval"},{"commits":2,"contributor":"Sebastian Kürten"},{"commits":2,"contributor":"Damian Tokarski"},{"commits":2,"contributor":"mic140"},{"commits":2,"contributor":"Heiko"},{"commits":2,"contributor":"Leo Alcaraz"},{"commits":1,"contributor":"sparky-oxford"},{"commits":1,"contributor":"jcn706"},{"commits":1,"contributor":"whatismoss"},{"commits":1,"contributor":"LePirlouit"},{"commits":1,"contributor":"SoftwareByRedline"},{"commits":1,"contributor":"plic ploc"},{"commits":1,"contributor":"Janina Ellinghaus"},{"commits":1,"contributor":"ssantos"},{"commits":1,"contributor":"Andre Fajar N"},{"commits":1,"contributor":"Ahen Purwakarta"},{"commits":1,"contributor":"Luna Jernberg"},{"commits":1,"contributor":"Rodrigo Tavares"},{"commits":1,"contributor":"liimee"},{"commits":1,"contributor":"Michał Targoński"},{"commits":1,"contributor":"Sean Young"},{"commits":1,"contributor":"Damian Pułka"},{"commits":1,"contributor":"Iváns"},{"commits":1,"contributor":"快乐的老鼠宝宝"},{"commits":1,"contributor":"Eric Armijo"},{"commits":1,"contributor":"Beardhatcode"},{"commits":1,"contributor":"riiga"},{"commits":1,"contributor":"Carlos Ramos Carreño"}]}
|
{"contributors":[{"commits":60,"contributor":"danieldegroot2"},{"commits":43,"contributor":"kjon"},{"commits":29,"contributor":"Artem"},{"commits":26,"contributor":"Pieter Vander Vennet"},{"commits":25,"contributor":"Babos Gábor"},{"commits":22,"contributor":"Supaplex"},{"commits":22,"contributor":"Marco"},{"commits":22,"contributor":"Allan Nordhøy"},{"commits":21,"contributor":"Anonymous"},{"commits":15,"contributor":"WaldiS"},{"commits":14,"contributor":"Reza Almanda"},{"commits":14,"contributor":"J. Lavoie"},{"commits":13,"contributor":"SC"},{"commits":10,"contributor":"Robin van der Linde"},{"commits":9,"contributor":"Jacque Fresco"},{"commits":8,"contributor":"Joost"},{"commits":8,"contributor":"LeJun"},{"commits":8,"contributor":"Irina"},{"commits":6,"contributor":"Štefan Baebler"},{"commits":6,"contributor":"seppesantens"},{"commits":6,"contributor":"Nikolay Korotkiy"},{"commits":6,"contributor":"William Weber Berrutti"},{"commits":6,"contributor":"lvgx"},{"commits":5,"contributor":"Romain de Bossoreille"},{"commits":5,"contributor":"Piotr"},{"commits":5,"contributor":"Vinicius"},{"commits":5,"contributor":"Alexey Shabanov"},{"commits":4,"contributor":"Jeff Huang"},{"commits":4,"contributor":"Adolfo Jayme Barrientos"},{"commits":4,"contributor":"Polgár Sándor"},{"commits":4,"contributor":"David Haberthür"},{"commits":4,"contributor":"phlostically"},{"commits":4,"contributor":"Jan Zabel"},{"commits":4,"contributor":"Fabio Bettani"},{"commits":3,"contributor":"Sasha"},{"commits":3,"contributor":"Jose Luis Infante"},{"commits":3,"contributor":"Francois"},{"commits":3,"contributor":"Eduardo Addad de Oliveira"},{"commits":3,"contributor":"Wiktor Przybylski"},{"commits":3,"contributor":"Erik Palm"},{"commits":3,"contributor":"vankos"},{"commits":3,"contributor":"JCGF-OSM"},{"commits":3,"contributor":"Hiroshi Miura"},{"commits":3,"contributor":"SiegbjornSitumeang"},{"commits":2,"contributor":"MeblIkea"},{"commits":2,"contributor":"快乐的老鼠宝宝"},{"commits":2,"contributor":"わたなべけんご"},{"commits":2,"contributor":"Mateusz Konieczny"},{"commits":2,"contributor":"Kristoffer Grundström"},{"commits":2,"contributor":"el_libre como el chaval"},{"commits":2,"contributor":"Sebastian Kürten"},{"commits":2,"contributor":"Damian Tokarski"},{"commits":2,"contributor":"mic140"},{"commits":2,"contributor":"Heiko"},{"commits":2,"contributor":"Leo Alcaraz"},{"commits":1,"contributor":"Falk Rund"},{"commits":1,"contributor":"pdassori"},{"commits":1,"contributor":"sparky-oxford"},{"commits":1,"contributor":"jcn706"},{"commits":1,"contributor":"whatismoss"},{"commits":1,"contributor":"LePirlouit"},{"commits":1,"contributor":"SoftwareByRedline"},{"commits":1,"contributor":"plic ploc"},{"commits":1,"contributor":"Janina Ellinghaus"},{"commits":1,"contributor":"ssantos"},{"commits":1,"contributor":"Andre Fajar N"},{"commits":1,"contributor":"Ahen Purwakarta"},{"commits":1,"contributor":"Luna Jernberg"},{"commits":1,"contributor":"Rodrigo Tavares"},{"commits":1,"contributor":"liimee"},{"commits":1,"contributor":"Michał Targoński"},{"commits":1,"contributor":"Sean Young"},{"commits":1,"contributor":"Damian Pułka"},{"commits":1,"contributor":"Iváns"},{"commits":1,"contributor":"Eric Armijo"},{"commits":1,"contributor":"Beardhatcode"},{"commits":1,"contributor":"riiga"},{"commits":1,"contributor":"Carlos Ramos Carreño"}]}
|
|
@ -1040,6 +1040,10 @@ video {
|
||||||
height: 50%;
|
height: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-4 {
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-screen {
|
.h-screen {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
@ -1060,10 +1064,6 @@ video {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-4 {
|
|
||||||
height: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-0 {
|
.h-0 {
|
||||||
height: 0px;
|
height: 0px;
|
||||||
}
|
}
|
||||||
|
@ -1132,6 +1132,10 @@ video {
|
||||||
width: 0px;
|
width: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-4 {
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-screen {
|
.w-screen {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
@ -1140,10 +1144,6 @@ video {
|
||||||
width: 2.75rem;
|
width: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-4 {
|
|
||||||
width: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-16 {
|
.w-16 {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
|
@ -1412,6 +1412,11 @@ video {
|
||||||
border-color: rgba(0, 0, 0, var(--tw-border-opacity));
|
border-color: rgba(0, 0, 0, var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-gray-400 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgba(156, 163, 175, var(--tw-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.border-gray-300 {
|
.border-gray-300 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||||
|
@ -1422,11 +1427,6 @@ video {
|
||||||
border-color: rgba(252, 165, 165, var(--tw-border-opacity));
|
border-color: rgba(252, 165, 165, var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-gray-400 {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgba(156, 163, 175, var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-200 {
|
.border-gray-200 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgba(229, 231, 235, var(--tw-border-opacity));
|
border-color: rgba(229, 231, 235, var(--tw-border-opacity));
|
||||||
|
@ -1441,6 +1441,11 @@ video {
|
||||||
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
|
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-red-400 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(248, 113, 113, var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-gray-400 {
|
.bg-gray-400 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgba(156, 163, 175, var(--tw-bg-opacity));
|
background-color: rgba(156, 163, 175, var(--tw-bg-opacity));
|
||||||
|
@ -1518,6 +1523,10 @@ video {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pl-1 {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-2 {
|
.pl-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1534,10 +1543,6 @@ video {
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pl-1 {
|
|
||||||
padding-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pr-1 {
|
.pr-1 {
|
||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1908,6 +1913,10 @@ svg, img {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.weblate-link {
|
||||||
|
/* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */
|
||||||
|
}
|
||||||
|
|
||||||
.mapcontrol svg path {
|
.mapcontrol svg path {
|
||||||
fill: var(--subtle-detail-color-contrast) !important;
|
fill: var(--subtle-detail-color-contrast) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,10 @@ svg, img {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.weblate-link {
|
||||||
|
/* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */
|
||||||
|
}
|
||||||
|
|
||||||
.mapcontrol svg path {
|
.mapcontrol svg path {
|
||||||
fill: var(--subtle-detail-color-contrast) !important;
|
fill: var(--subtle-detail-color-contrast) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,6 +246,10 @@
|
||||||
"loading": "Loading Wikipedia...",
|
"loading": "Loading Wikipedia...",
|
||||||
"noResults": "Nothing found for <i>{search}</i>",
|
"noResults": "Nothing found for <i>{search}</i>",
|
||||||
"noWikipediaPage": "This Wikidata item has no corresponding Wikipedia page yet.",
|
"noWikipediaPage": "This Wikidata item has no corresponding Wikipedia page yet.",
|
||||||
|
"previewbox": {
|
||||||
|
"born": "Born: {value}",
|
||||||
|
"died": "Died: {value}"
|
||||||
|
},
|
||||||
"searchWikidata": "Search on Wikidata",
|
"searchWikidata": "Search on Wikidata",
|
||||||
"wikipediaboxTitle": "Wikipedia"
|
"wikipediaboxTitle": "Wikipedia"
|
||||||
}
|
}
|
||||||
|
@ -359,6 +363,7 @@
|
||||||
"autoApply": "When changing the attributes {attr_names}, these attributes will automatically be changed on {count} other objects too"
|
"autoApply": "When changing the attributes {attr_names}, these attributes will automatically be changed on {count} other objects too"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
|
"addAComment": "Add a comment",
|
||||||
"addComment": "Add comment",
|
"addComment": "Add comment",
|
||||||
"addCommentAndClose": "Add comment and close",
|
"addCommentAndClose": "Add comment and close",
|
||||||
"addCommentPlaceholder": "Add a comment...",
|
"addCommentPlaceholder": "Add a comment...",
|
||||||
|
@ -525,6 +530,15 @@
|
||||||
"split": "Split",
|
"split": "Split",
|
||||||
"splitTitle": "Choose on the map where to split this road"
|
"splitTitle": "Choose on the map where to split this road"
|
||||||
},
|
},
|
||||||
|
"translations": {
|
||||||
|
"activateButton": "Help to translate MapComplete",
|
||||||
|
"completeness": "Translations for {theme} in {language} are at {percentage}%: {translated} strings out of {total} are translated",
|
||||||
|
"deactivate": "Disable translation buttons",
|
||||||
|
"help": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.",
|
||||||
|
"isTranslator": "Translation mode is active as your username matches the name of a previous translator",
|
||||||
|
"missing": "{count} untranslated strings",
|
||||||
|
"notImmediate": "Translations are not updated directly. This typically takes a few days"
|
||||||
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
"description": "A color or hexcode"
|
"description": "A color or hexcode"
|
||||||
|
|
|
@ -1062,6 +1062,9 @@
|
||||||
},
|
},
|
||||||
"question": "Are there tools here to repair your own bike?"
|
"question": "Are there tools here to repair your own bike?"
|
||||||
},
|
},
|
||||||
|
"bike_shop-access": {
|
||||||
|
"render": "Only accessible to {access}"
|
||||||
|
},
|
||||||
"bike_shop-email": {
|
"bike_shop-email": {
|
||||||
"question": "What is the email address of {name}?"
|
"question": "What is the email address of {name}?"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1062,6 +1062,9 @@
|
||||||
},
|
},
|
||||||
"question": "Biedt deze winkel gereedschap aan om je fiets zelf te herstellen?"
|
"question": "Biedt deze winkel gereedschap aan om je fiets zelf te herstellen?"
|
||||||
},
|
},
|
||||||
|
"bike_shop-access": {
|
||||||
|
"render": "Enkel voor {access}"
|
||||||
|
},
|
||||||
"bike_shop-email": {
|
"bike_shop-email": {
|
||||||
"question": "Wat is het email-adres van {name}?"
|
"question": "Wat is het email-adres van {name}?"
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {DesugaringContext} from "../Models/ThemeConfig/Conversion/Conversion";
|
||||||
|
|
||||||
class LayerOverviewUtils {
|
class LayerOverviewUtils {
|
||||||
|
|
||||||
writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean }[]) {
|
writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean, mustHaveLanguage: boolean }[]) {
|
||||||
const perId = new Map<string, any>();
|
const perId = new Map<string, any>();
|
||||||
for (const theme of themes) {
|
for (const theme of themes) {
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -27,7 +27,8 @@ class LayerOverviewUtils {
|
||||||
title: theme.title,
|
title: theme.title,
|
||||||
shortDescription: theme.shortDescription,
|
shortDescription: theme.shortDescription,
|
||||||
icon: theme.icon,
|
icon: theme.icon,
|
||||||
hideFromOverview: theme.hideFromOverview
|
hideFromOverview: theme.hideFromOverview,
|
||||||
|
mustHaveLanguage: theme.mustHaveLanguage
|
||||||
}
|
}
|
||||||
perId.set(theme.id, data);
|
perId.set(theme.id, data);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,7 @@ class LayerOverviewUtils {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
questions[key].id = key;
|
questions[key].id = key;
|
||||||
|
questions[key]["source"] = "shared-questions"
|
||||||
dict.set(key, <TagRenderingConfigJson>questions[key])
|
dict.set(key, <TagRenderingConfigJson>questions[key])
|
||||||
}
|
}
|
||||||
for (const key in icons["default"]) {
|
for (const key in icons["default"]) {
|
||||||
|
@ -218,7 +220,8 @@ class LayerOverviewUtils {
|
||||||
return {
|
return {
|
||||||
...t,
|
...t,
|
||||||
hideFromOverview: t.hideFromOverview ?? false,
|
hideFromOverview: t.hideFromOverview ?? false,
|
||||||
shortDescription: t.shortDescription ?? new Translation(t.description).FirstSentence().translations
|
shortDescription: t.shortDescription ?? new Translation(t.description).FirstSentence().translations,
|
||||||
|
mustHaveLanguage: t.mustHaveLanguage?.length > 0
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return fixed;
|
return fixed;
|
||||||
|
|
|
@ -244,11 +244,11 @@ function isTranslation(tr: any): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a translation object into something that can be added to the 'generated translations'
|
* Converts a translation object into something that can be added to the 'generated translations'.
|
||||||
* @param obj
|
*
|
||||||
* @param depth
|
* To debug the 'compiledTranslations', add a languageWhiteList to only generate a single language
|
||||||
*/
|
*/
|
||||||
function transformTranslation(obj: any, depth = 1) {
|
function transformTranslation(obj: any, path: string[] = [], languageWhitelist : string[] = undefined) {
|
||||||
|
|
||||||
if (isTranslation(obj)) {
|
if (isTranslation(obj)) {
|
||||||
return `new Translation( ${JSON.stringify(obj)} )`
|
return `new Translation( ${JSON.stringify(obj)} )`
|
||||||
|
@ -259,15 +259,24 @@ function transformTranslation(obj: any, depth = 1) {
|
||||||
if (key === "#") {
|
if (key === "#") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.match("^[a-zA-Z0-9_]*$") === null) {
|
if (key.match("^[a-zA-Z0-9_]*$") === null) {
|
||||||
throw "Invalid character in key: " + key
|
throw "Invalid character in key: " + key
|
||||||
}
|
}
|
||||||
const value = obj[key]
|
let value = obj[key]
|
||||||
|
|
||||||
if (isTranslation(value)) {
|
if (isTranslation(value)) {
|
||||||
values += (Utils.Times((_) => " ", depth)) + "get " + key + "() { return new Translation(" + JSON.stringify(value) + ") }" + ",\n"
|
if(languageWhitelist !== undefined){
|
||||||
|
const nv = {}
|
||||||
|
for (const ln of languageWhitelist) {
|
||||||
|
nv[ln] = value[ln]
|
||||||
|
}
|
||||||
|
value = nv;
|
||||||
|
}
|
||||||
|
values += `${Utils.Times((_) => " ", path.length + 1)}get ${key}() { return new Translation(${JSON.stringify(value)}, "core:${path.join(".")}.${key}") },
|
||||||
|
`
|
||||||
} else {
|
} else {
|
||||||
values += (Utils.Times((_) => " ", depth)) + key + ": " + transformTranslation(value, depth + 1) + ",\n"
|
values += (Utils.Times((_) => " ", path.length + 1)) + key + ": " + transformTranslation(value, [...path, key], languageWhitelist) + ",\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `{${values}}`;
|
return `{${values}}`;
|
||||||
|
@ -309,7 +318,7 @@ function genTranslations() {
|
||||||
|
|
||||||
let module = `import {Translation} from "../../UI/i18n/Translation"\n\nexport default class CompiledTranslations {\n\n`;
|
let module = `import {Translation} from "../../UI/i18n/Translation"\n\nexport default class CompiledTranslations {\n\n`;
|
||||||
module += " public static t = " + transformed;
|
module += " public static t = " + transformed;
|
||||||
module += "}"
|
module += "\n }"
|
||||||
|
|
||||||
fs.writeFileSync("./assets/generated/CompiledTranslations.ts", module);
|
fs.writeFileSync("./assets/generated/CompiledTranslations.ts", module);
|
||||||
|
|
||||||
|
@ -541,7 +550,7 @@ for (const path of allTranslationFiles) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SOme validation
|
// Some validation
|
||||||
TranslationPart.fromDirectory("./langs").validateStrict("./langs")
|
TranslationPart.fromDirectory("./langs").validateStrict("./langs")
|
||||||
TranslationPart.fromDirectory("./langs/layers").validateStrict("layers")
|
TranslationPart.fromDirectory("./langs/layers").validateStrict("layers")
|
||||||
TranslationPart.fromDirectory("./langs/themes").validateStrict("themes")
|
TranslationPart.fromDirectory("./langs/themes").validateStrict("themes")
|
||||||
|
|
Loading…
Reference in a new issue