import { UIEventSource } from "../Logic/UIEventSource" import { Translation } from "./i18n/Translation" import Locale from "./i18n/Locale" import { FixedUiElement } from "./Base/FixedUiElement" import SpecialVisualizations from "./SpecialVisualizations" import { Utils } from "../Utils" import { VariableUiElement } from "./Base/VariableUIElement" import Combine from "./Base/Combine" import BaseUIElement from "./BaseUIElement" import { DefaultGuiState } from "./DefaultGuiState" import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import LinkToWeblate from "./Base/LinkToWeblate" import {SpecialVisualization} from "./SpecialVisualization"; export class SubstitutedTranslation extends VariableUiElement { public constructor( translation: Translation, tagsSource: UIEventSource>, state: FeaturePipelineState, mapping: Map< string, | BaseUIElement | (( state: FeaturePipelineState, tagSource: UIEventSource>, argument: string[], guistate: DefaultGuiState ) => BaseUIElement) > = undefined ) { const extraMappings: SpecialVisualization[] = [] mapping?.forEach((value, key) => { extraMappings.push({ funcName: key, constr: typeof value === "function" ? value : () => value, docs: "Dynamically injected input element", args: [], example: "", }) }) const linkToWeblate = translation !== undefined ? new LinkToWeblate(translation.context, translation.translations) : undefined super( Locale.language.map((language) => { let txt = translation?.textFor(language) if (txt === undefined) { return undefined } mapping?.forEach((_, key) => { txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`) }) const allElements = SubstitutedTranslation.ExtractSpecialComponents( txt, extraMappings ).map((proto) => { if (proto.fixed !== undefined) { if (tagsSource === undefined) { return Utils.SubstituteKeys(proto.fixed, undefined) } return new VariableUiElement( tagsSource.map((tags) => Utils.SubstituteKeys(proto.fixed, tags)) ) } const viz = proto.special if (viz === undefined) { console.error( "SPECIALRENDERING UNDEFINED for", tagsSource.data?.id, "THIS IS REALLY WEIRD" ) return undefined } try { return viz.func .constr(state, tagsSource, proto.special.args, DefaultGuiState.state) ?.SetStyle(proto.special.style) } catch (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") } }) allElements.push(linkToWeblate) return new Combine(allElements) }) ) this.SetClass("w-full") } /** * * // Return empty list on empty input * SubstitutedTranslation.ExtractSpecialComponents("") // => [] * * // Advanced cases with commas, braces and newlines should be handled without problem * const templates = SubstitutedTranslation.ExtractSpecialComponents("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}") * const templ = templates[0] * templ.special.func.funcName // => "send_email" * templ.special.args[0] = "{email}" */ public static ExtractSpecialComponents( template: string, extraMappings: SpecialVisualization[] = [] ): { fixed?: string special?: { func: SpecialVisualization args: string[] style: string } }[] { if (template === "") { return [] } for (const knownSpecial of extraMappings.concat( SpecialVisualizations.specialVisualizations )) { // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' const matched = template.match( new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s") ) if (matched != null) { // We found a special component that should be brought to live const partBefore = SubstitutedTranslation.ExtractSpecialComponents( matched[1], extraMappings ) const argument = matched[2].trim() const style = matched[3]?.substring(1) ?? "" const partAfter = SubstitutedTranslation.ExtractSpecialComponents( matched[4], extraMappings ) const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "") if (argument.length > 0) { const realArgs = argument.split(",").map((str) => str .trim() .replace(/&LPARENS/g, "(") .replace(/&RPARENS/g, ")") .replace(/&LBRACE/g, "{") .replace(/&RBRACE/g, "}") .replace(/&COMMA/g, ",") ) for (let i = 0; i < realArgs.length; i++) { if (args.length <= i) { args.push(realArgs[i]) } else { args[i] = realArgs[i] } } } let element element = { special: { args: args, style: style, func: knownSpecial, }, } return [...partBefore, element, ...partAfter] } } // Let's to a small sanity check to help the theme designers: if (template.search(/{[^}]+\([^}]*\)}/) >= 0) { // Hmm, we might have found an invalid rendering name console.warn( "Found a suspicious special rendering value in: ", template, " did you mean one of: ", SpecialVisualizations.specialVisualizations .map((sp) => sp.funcName + "()") .join(", ") ) } // IF we end up here, no changes have to be made - except to remove any resting {} return [{ fixed: template }] } }