--- a/LPCSVGHMI/analyse_widget.xslt Mon Jan 26 11:31:16 2026 +0100
+++ b/LPCSVGHMI/analyse_widget.xslt Fri Jan 30 08:41:19 2026 +0100
@@ -1209,6 +1209,28 @@
<xsl:text>format string for Y label</xsl:text>
+ <xsl:template match="widget[@type='MultiLangJsonTable']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>Send given variables as POST to http URL argument, spread returned JSON in + <xsl:text>SVG sub-elements of "data" labeled element. + <xsl:text>Documentation to be written. see svghmi example. + <xsl:text>Http POST variables, spread JSON back</xsl:text> + <arg name="url" accepts="string"/> + <path name="edit" accepts="HMI_INT, HMI_REAL, HMI_STRING"> + <xsl:text>single variable to edit</xsl:text> <xsl:template match="widget[@type='Swipe']" mode="widget_desc">
<xsl:value-of select="@type"/>
--- a/LPCSVGHMI/gen_index_xhtml.xslt Mon Jan 26 11:31:16 2026 +0100
+++ b/LPCSVGHMI/gen_index_xhtml.xslt Fri Jan 30 08:41:19 2026 +0100
@@ -10159,6 +10159,620 @@
+ <xsl:template match="widget[@type='MultiLangJsonTable']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>Send given variables as POST to http URL argument, spread returned JSON in + <xsl:text>SVG sub-elements of "data" labeled element. + <xsl:text>Documentation to be written. see svghmi example. + <xsl:text>Http POST variables, spread JSON back</xsl:text> + <arg name="url" accepts="string"/> + <path name="edit" accepts="HMI_INT, HMI_REAL, HMI_STRING"> + <xsl:text>single variable to edit</xsl:text> + <xsl:template match="widget[@type='MultiLangJsonTable']" mode="widget_class"> + <xsl:text>class </xsl:text> + <xsl:text>MultiLangJsonTableWidget</xsl:text> + <xsl:text> extends Widget{ + <xsl:text> // arbitrary defaults to avoid missing entries in query + <xsl:text> cache = [0,0,0]; + <xsl:text> init_common() { + <xsl:text> this.spread_json_data_bound = this.spread_json_data.bind(this); + <xsl:text> this.handle_http_response_bound = this.handle_http_response.bind(this); + <xsl:text> this.fetch_error_bound = this.fetch_error.bind(this); + <xsl:text> if (this.should_translate === undefined) { + <xsl:text> this.should_translate = []; + <xsl:text> if (this.lang_keys === undefined) { + <xsl:text> this.lang_keys = []; + <xsl:text> this.promised = false; + <xsl:text> handle_http_response(response) { + <xsl:text> if (!response.ok) { + <xsl:text> console.log("HTTP error, status = " + response.status); + <xsl:text> return response.json(); + <xsl:text> fetch_error(e){ + <xsl:text> console.log("HTTP fetch error, message = " + e.message + "Widget:" + this.element_id); + <xsl:text> do_http_request(...opt) { + <xsl:text> this.abort_controller = new AbortController(); + <xsl:text> return Promise.resolve().then(() => { + <xsl:text> const query = { + <xsl:text> args: this.args, + <xsl:text> range: this.cache[1], + <xsl:text> position: this.cache[2], + <xsl:text> visible: this.visible, + <xsl:text> extra: this.cache.slice(4), + <xsl:text> options: opt + <xsl:text> const options = { + <xsl:text> method: 'POST', + <xsl:text> body: JSON.stringify(query), + <xsl:text> headers: {'Content-Type': 'application/json'}, + <xsl:text> signal: this.abort_controller.signal + <xsl:text> return fetch(this.args[0], options) + <xsl:text> .then(this.handle_http_response_bound) + <xsl:text> .then(this.spread_json_data_bound) + <xsl:text> .catch(this.fetch_error_bound); + <xsl:text> this.abort_controller.abort(); + <xsl:text> super.unsub(); + <xsl:text> sub(...args){ + <xsl:text> this.cache[0] = undefined; + <xsl:text> super.sub(...args); + <xsl:text> dispatch(value, oldval, index) { + <xsl:text> if(this.cache[index] != value) + <xsl:text> this.cache[index] = value; + <xsl:text> if(!this.promised){ + <xsl:text> this.promised = true; + <xsl:text> this.do_http_request().finally(() => { + <xsl:text> this.promised = false; + <xsl:text> make_on_click(...options){ + <xsl:text> let that = this; + <xsl:text> return function(evt){ + <xsl:text> that.do_http_request(...options); + <xsl:text> // on_click(evt, ...options) { + <xsl:text> // this.do_http_request(...options); + <xsl:template mode="json_table_elt_render" match="svg:*"> + <xsl:message terminate="yes"> + <xsl:text>MultiLangJsonTable Widget can't contain element of type </xsl:text> + <xsl:value-of select="local-name()"/> + <func:function name="func:ml_json_expressions"> + <xsl:param name="expressions"/> + <xsl:param name="label"/> + <xsl:when test="$label"> + <xsl:variable name="suffixes" select="str:split($label)"/> + <xsl:variable name="res"> + <xsl:for-each select="$suffixes"> + <xsl:variable name="suffix" select="."/> + <xsl:variable name="pos" select="position()"/> + <xsl:variable name="expr" select="$expressions[position() <= $pos][last()]/expression"/> + <xsl:if test="$pos = 1"> + <xsl:variable name="raw_selector" select="$suffix"/> + <xsl:variable name="lang_selector"> + <xsl:when test="starts-with($raw_selector, '.')"> + <xsl:value-of select="substring($raw_selector, 2)"/> + <xsl:value-of select="$raw_selector"/> + <xsl:attribute name="lang_selector"> + <xsl:value-of select="$lang_selector"/> + <xsl:when test="contains($suffix, '=')"> + <xsl:variable name="name" select="substring-before($suffix, '=')"/> + <xsl:variable name="content_raw" select="substring-after($suffix, '=')"/> + <xsl:if test="$expr/@name[. != $name]"> + <xsl:message terminate="yes"> + <xsl:text>MultiLangJsonTable : misplaced '=' or inconsistent names in Json data expressions.</xsl:text> + <xsl:attribute name="name"> + <xsl:value-of select="$name"/> + <xsl:when test="starts-with($content_raw, '_(') and substring($content_raw, string-length($content_raw)) = ')'"> + <xsl:variable name="raw_key" select="substring($content_raw, 3, string-length($content_raw) - 3)"/> + <xsl:variable name="clean_key"> + <xsl:when test="starts-with($raw_key, '.')"> + <xsl:value-of select="substring($raw_key, 2)"/> + <xsl:value-of select="$raw_key"/> + <xsl:attribute name="translation_key"> + <xsl:value-of select="$clean_key"/> + <xsl:attribute name="content"> + <xsl:value-of select="$expr/@content"/> + <xsl:value-of select="$raw_key"/> + <xsl:attribute name="content"> + <xsl:value-of select="$expr/@content"/> + <xsl:value-of select="$content_raw"/> + <xsl:copy-of select="$expr/@name"/> + <xsl:attribute name="content"> + <xsl:value-of select="$expr/@content"/> + <xsl:value-of select="$suffix"/> + <func:result select="exsl:node-set($res)"/> + <func:result select="$expressions"/> + <xsl:variable name="ml_initexpr"> + <xsl:attribute name="content"> + <xsl:text>jdata</xsl:text> + <xsl:variable name="ml_initexpr_ns" select="exsl:node-set($ml_initexpr)"/> + <xsl:template mode="json_table_elt_render" match="svg:use"> + <xsl:param name="expressions"/> + <xsl:variable name="targetid" select="substring-after(@xlink:href,'#')"/> + <xsl:variable name="from_list" select="$hmi_lists[(@id | */@id) = $targetid]"/> + <xsl:when test="count($from_list) > 0"> + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").href.baseVal = + <xsl:text> "#"+hmi_widgets["</xsl:text> + <xsl:value-of select="$from_list/@id"/> + <xsl:text>"].items[</xsl:text> + <xsl:value-of select="$expressions/expression[1]/@content"/> + <xsl:message terminate="no"> + <xsl:text>Clones (svg:use) in MultiLangJsonTable Widget must point to a valid HMI:List widget or item. Reference "</xsl:text> + <xsl:value-of select="@xlink:href"/> + <xsl:text>" is not valid and will not be updated.</xsl:text> + <xsl:template mode="json_table_elt_render" match="svg:text"> + <xsl:param name="expressions"/> + <xsl:variable name="value_expr" select="$expressions/expression[1]/@content"/> + <xsl:variable name="original" select="@original"/> + <xsl:variable name="from_textstylelist" select="$textstylelist_related_ns/list[elt/@eltid = $original]"/> + <xsl:when test="count($from_textstylelist) > 0"> + <xsl:variable name="content_expr" select="$expressions/expression[2]/@content"/> + <xsl:if test="string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'"> + <xsl:message terminate="yes"> + <xsl:text>Clones (svg:use) in MultiLangJsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignment following value expression in label.</xsl:text> + <xsl:text> let elt = id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text> elt.textContent = String(</xsl:text> + <xsl:value-of select="$content_expr"/> + <xsl:text> elt.style = hmi_widgets["</xsl:text> + <xsl:value-of select="$from_textstylelist/@listid"/> + <xsl:text>"].styles[</xsl:text> + <xsl:value-of select="$value_expr"/> + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").textContent = String(</xsl:text> + <xsl:value-of select="$value_expr"/> + <xsl:template mode="json_table_elt_render" match="svg:image"> + <xsl:param name="expressions"/> + <xsl:variable name="value_expr" select="$expressions/expression[1]/@content"/> + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").setAttribute('href', String(</xsl:text> + <xsl:value-of select="$value_expr"/> + <xsl:template mode="json_table_render_except_comments" match="svg:*"> + <xsl:param name="expressions"/> + <xsl:param name="widget_elts"/> + <xsl:variable name="label" select="func:filter_non_widget_label(., $widget_elts)"/> + <xsl:if test="not(starts-with($label,'#'))"> + <xsl:apply-templates mode="json_table_render" select="."> + <xsl:with-param name="expressions" select="$expressions"/> + <xsl:with-param name="widget_elts" select="$widget_elts"/> + <xsl:with-param name="label" select="$label"/> + <xsl:template mode="json_table_render" match="svg:*"> + <xsl:param name="expressions"/> + <xsl:param name="widget_elts"/> + <xsl:param name="label"/> + <xsl:variable name="new_expressions" select="func:ml_json_expressions($expressions, $label)"/> + <xsl:variable name="elt" select="."/> + <xsl:for-each select="$new_expressions/expression[position() > 1][starts-with(@name,'onClick')]"> + <xsl:text> id("</xsl:text> + <xsl:value-of select="$elt/@id"/> + <xsl:text>").onclick = this.make_on_click('</xsl:text> + <xsl:value-of select="@name"/> + <xsl:text>', </xsl:text> + <xsl:value-of select="@content"/> + <xsl:apply-templates mode="json_table_elt_render" select="."> + <xsl:with-param name="expressions" select="$new_expressions"/> + <xsl:template mode="json_table_render" match="svg:g"> + <xsl:param name="expressions"/> + <xsl:param name="widget_elts"/> + <xsl:param name="label"/> + <xsl:variable name="varprefix"> + <xsl:text>obj_</xsl:text> + <xsl:value-of select="@id"/> + <xsl:for-each select="$expressions/expression"> + <xsl:text> let </xsl:text> + <xsl:value-of select="$varprefix"/> + <xsl:value-of select="position()"/> + <xsl:text> = </xsl:text> + <xsl:value-of select="@content"/> + <xsl:text> if(</xsl:text> + <xsl:value-of select="$varprefix"/> + <xsl:value-of select="position()"/> + <xsl:text> == undefined) { + <xsl:variable name="new_expressions"> + <xsl:for-each select="$expressions/expression"> + <xsl:copy-of select="@name"/> + <xsl:attribute name="content"> + <xsl:value-of select="$varprefix"/> + <xsl:value-of select="position()"/> + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").style = "</xsl:text> + <xsl:value-of select="@style"/> + <xsl:apply-templates mode="json_table_render_except_comments" select="*"> + <xsl:with-param name="expressions" select="func:ml_json_expressions(exsl:node-set($new_expressions), $label)"/> + <xsl:with-param name="widget_elts" select="$widget_elts"/> + <xsl:text> } catch(err) { + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").style = "display:none"; + <xsl:template match="widget[@type='MultiLangJsonTable']" mode="widget_defs"> + <xsl:param name="hmi_element"/> + <xsl:variable name="disability"> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>/disabled</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> + <xsl:value-of select="$disability"/> + <xsl:variable name="has_disability" select="string-length($disability)>0"/> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>data</xsl:text> + <xsl:variable name="data_elt" select="$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']"/> + <xsl:variable name="widget_elts" select="$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*"/> + <xsl:variable name="all_parsed_expressions"> + <xsl:for-each select="$data_elt/descendant-or-self::svg:*[@inkscape:label]"> + <xsl:variable name="exprs" select="func:ml_json_expressions($ml_initexpr_ns, @inkscape:label)"/> + <xsl:copy-of select="exsl:node-set($exprs)//expression[@translation_key or @lang_selector]"/> + <xsl:variable name="all_exprs_ns" select="exsl:node-set($all_parsed_expressions)"/> + <xsl:text> visible: </xsl:text> + <xsl:value-of select="count($data_elt/*[@inkscape:label])"/> + <xsl:text> should_translate: [ + <xsl:for-each select="$all_exprs_ns/expression[@translation_key]"> + <xsl:if test="not(@translation_key = preceding-sibling::expression/@translation_key)"> + <xsl:text> "</xsl:text> + <xsl:value-of select="@translation_key"/> + <xsl:if test="position() != last()"> + <xsl:text> lang_keys: [ + <xsl:for-each select="$all_exprs_ns/expression[@lang_selector]"> + <xsl:if test="not(@lang_selector = preceding-sibling::expression/@lang_selector)"> + <xsl:text> "</xsl:text> + <xsl:value-of select="@lang_selector"/> + <xsl:if test="position() != last()"> + <xsl:text> spread_json_data: function(janswer) { + <xsl:text> let [range,position,jdata] = janswer; + <xsl:text> if (jdata.length > 0 && this.should_translate.length > 0) { + <xsl:text> const lang = cache[lang_local_index]; + <xsl:text> const langcode = langs[lang][1]; + <xsl:text> for (let row of jdata) { + <xsl:text> for (const key of this.should_translate) { + <xsl:text> if (key in row) { + <xsl:text> const orig = row[key]; + <xsl:text> const match = translations.find(item => item[1][0] == orig); + <xsl:text> const tr = match ? match[1][lang] : orig; + <xsl:text> row[key] = tr; + <xsl:text> for (const key of this.lang_keys) { + <xsl:text> if (key in row) { + <xsl:text> row[key] = String(row[key]) + "_" + langcode; + <xsl:text> [[1, range], [2, position], [3, this.visible]].map(([i,v]) => { + <xsl:text> this.apply_hmi_value(i,v); + <xsl:text> this.cache[i] = v; + <xsl:apply-templates mode="json_table_render_except_comments" select="$data_elt"> + <xsl:with-param name="expressions" select="$ml_initexpr_ns"/> + <xsl:with-param name="widget_elts" select="$widget_elts"/> + <xsl:text> this.init_common(); + <xsl:for-each select="$hmi_element/*[starts-with(@inkscape:label,'action_')]"> + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").onclick = this.make_on_click("</xsl:text> + <xsl:value-of select="func:escape_quotes(@inkscape:label)"/> + <xsl:for-each select="$hmi_element/*[starts-with(@inkscape:label,'dict')]"> + <xsl:text> id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>").style.display = "none"; <xsl:template match="widget[@type='Swipe']" mode="widget_desc">
<xsl:value-of select="@type"/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LPCSVGHMI/widget_multilangjsontable.ysl2 Fri Jan 30 08:41:19 2026 +0100
@@ -0,0 +1,358 @@
+// widget_multilangjsontable.ysl2 +widget_desc("MultiLangJsonTable") { + Send given variables as POST to http URL argument, spread returned JSON in + SVG sub-elements of "data" labeled element. + Documentation to be written. see svghmi example. + shortdesc > Http POST variables, spread JSON back + arg name="url" accepts="string" > + path name="edit" accepts="HMI_INT, HMI_REAL, HMI_STRING" > single variable to edit +widget_class("MultiLangJsonTable") + // arbitrary defaults to avoid missing entries in query + this.spread_json_data_bound = this.spread_json_data.bind(this); + this.handle_http_response_bound = this.handle_http_response.bind(this); + this.fetch_error_bound = this.fetch_error.bind(this); + if (this.should_translate === undefined) { + this.should_translate = []; + if (this.lang_keys === undefined) { + handle_http_response(response) { + console.log("HTTP error, status = " + response.status); + return response.json(); + console.log("HTTP fetch error, message = " + e.message + "Widget:" + this.element_id); + do_http_request(...opt) { + this.abort_controller = new AbortController(); + return Promise.resolve().then(() => { + position: this.cache[2], + extra: this.cache.slice(4), + body: JSON.stringify(query), + headers: {'Content-Type': 'application/json'}, + signal: this.abort_controller.signal + return fetch(this.args[0], options) + .then(this.handle_http_response_bound) + .then(this.spread_json_data_bound) + .catch(this.fetch_error_bound); + this.abort_controller.abort(); + this.cache[0] = undefined; + dispatch(value, oldval, index) { + if(this.cache[index] != value) + this.cache[index] = value; + this.do_http_request().finally(() => { + make_on_click(...options){ + that.do_http_request(...options); + // on_click(evt, ...options) { + // this.do_http_request(...options); +template "svg:*", mode="json_table_elt_render" { + error > MultiLangJsonTable Widget can't contain element of type «local-name()». +def "func:ml_json_expressions" { + const "suffixes", "str:split($label)"; + const "res" foreach "$suffixes" expression { + const "pos", "position()"; + const "expr", "$expressions[position() <= $pos][last()]/expression"; + const "raw_selector", "$suffix"; + const "lang_selector" choose { + when "starts-with($raw_selector, '.')" value "substring($raw_selector, 2)"; + otherwise value "$raw_selector"; + attrib "lang_selector" value "$lang_selector"; + when "contains($suffix, '=')" { + const "name", "substring-before($suffix, '=')"; + const "content_raw", "substring-after($suffix, '=')"; + if "$expr/@name[. != $name]" + error > MultiLangJsonTable : misplaced '=' or inconsistent names in Json data expressions. + attrib "name" value "$name"; + when "starts-with($content_raw, '_(') and substring($content_raw, string-length($content_raw)) = ')'" { + const "raw_key", "substring($content_raw, 3, string-length($content_raw) - 3)"; + const "clean_key" choose { + when "starts-with($raw_key, '.')" value "substring($raw_key, 2)"; + otherwise value "$raw_key"; + attrib "translation_key" value "$clean_key"; + attrib "content" > «$expr/@content»«$raw_key» + attrib "content" > «$expr/@content»«$content_raw» + attrib "content" > «$expr/@content»«$suffix» + result "exsl:node-set($res)"; + otherwise result "$expressions"; +const "ml_initexpr" expression attrib "content" > jdata +const "ml_initexpr_ns", "exsl:node-set($ml_initexpr)"; +template "svg:use", mode="json_table_elt_render" { + // cloned element must be part of a HMI:List or a HMI:List + const "targetid", "substring-after(@xlink:href,'#')"; + const "from_list", "$hmi_lists[(@id | */@id) = $targetid]"; + when "count($from_list) > 0" { + | id("«@id»").href.baseVal = + // obtain new target id from HMI:List widget + | "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]; + warning > Clones (svg:use) in MultiLangJsonTable Widget must point to a valid HMI:List widget or item. Reference "«@xlink:href»" is not valid and will not be updated. +template "svg:text", mode="json_table_elt_render" { + const "value_expr", "$expressions/expression[1]/@content"; + const "original", "@original"; + const "from_textstylelist", "$textstylelist_related_ns/list[elt/@eltid = $original]"; + when "count($from_textstylelist) > 0" { + const "content_expr", "$expressions/expression[2]/@content"; + if "string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'" + error > Clones (svg:use) in MultiLangJsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignment following value expression in label. + | let elt = id("«@id»"); + | elt.textContent = String(«$content_expr»); + | elt.style = hmi_widgets["«$from_textstylelist/@listid»"].styles[«$value_expr»]; + | id("«@id»").textContent = String(«$value_expr»); +template "svg:image", mode="json_table_elt_render" { + const "value_expr", "$expressions/expression[1]/@content"; + | id("«@id»").setAttribute('href', String(«$value_expr»)); +template "svg:*", mode="json_table_render_except_comments"{ + const "label", "func:filter_non_widget_label(., $widget_elts)"; + // filter out "# commented" elements + if "not(starts-with($label,'#'))" + apply ".", mode="json_table_render"{ + with "expressions", "$expressions"; + with "widget_elts", "$widget_elts"; + with "label", "$label"; +template "svg:*", mode="json_table_render" { + const "new_expressions", "func:ml_json_expressions($expressions, $label)"; + foreach "$new_expressions/expression[position() > 1][starts-with(@name,'onClick')]" + | id("«$elt/@id»").onclick = this.make_on_click('«@name»', «@content»); + apply ".", mode="json_table_elt_render" + with "expressions", "$new_expressions"; +template "svg:g", mode="json_table_render" { + // use intermediate variables for optimization + const "varprefix" > obj_«@id»_ + foreach "$expressions/expression"{ + | let «$varprefix»«position()» = «@content»; + | if(«$varprefix»«position()» == undefined) { + // because we put values in a variables, we can replace corresponding expression with variable name + const "new_expressions" foreach "$expressions/expression" xsl:copy { + attrib "content" > «$varprefix»«position()» + // revert hiding in case it did happen before + | id("«@id»").style = "«@style»"; + apply "*", mode="json_table_render_except_comments" { + with "expressions", "func:ml_json_expressions(exsl:node-set($new_expressions), $label)"; + with "widget_elts", "$widget_elts"; + | id("«@id»").style = "display:none"; +widget_defs("MultiLangJsonTable") { + const "data_elt", "$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']"; + const "widget_elts", "$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*"; + const "all_parsed_expressions" { + foreach "$data_elt/descendant-or-self::svg:*[@inkscape:label]" { + const "exprs", "func:ml_json_expressions($ml_initexpr_ns, @inkscape:label)"; + copy "exsl:node-set($exprs)//expression[@translation_key or @lang_selector]"; + const "all_exprs_ns", "exsl:node-set($all_parsed_expressions)"; + | visible: «count($data_elt/*[@inkscape:label])», + foreach "$all_exprs_ns/expression[@translation_key]" { + if "not(@translation_key = preceding-sibling::expression/@translation_key)" { + if "position() != last()" { + foreach "$all_exprs_ns/expression[@lang_selector]" { + if "not(@lang_selector = preceding-sibling::expression/@lang_selector)" { + if "position() != last()" { + | spread_json_data: function(janswer) { + | let [range,position,jdata] = janswer; + | if (jdata.length > 0 && this.should_translate.length > 0) { + | const lang = cache[lang_local_index]; + | const langcode = langs[lang][1]; + | for (let row of jdata) { + | for (const key of this.should_translate) { + | const orig = row[key]; + | const match = translations.find(item => item[1][0] == orig); + | const tr = match ? match[1][lang] : orig; + | for (const key of this.lang_keys) { + | row[key] = String(row[key]) + "_" + langcode; + | [[1, range], [2, position], [3, this.visible]].map(([i,v]) => { + | this.apply_hmi_value(i,v); + apply "$data_elt", mode="json_table_render_except_comments" { + with "expressions","$ml_initexpr_ns"; + with "widget_elts","$widget_elts"; + foreach "$hmi_element/*[starts-with(@inkscape:label,'action_')]" { + | id("«@id»").onclick = this.make_on_click("«func:escape_quotes(@inkscape:label)»"); + foreach "$hmi_element/*[starts-with(@inkscape:label,'dict')]" { + | id("«@id»").style.display = "none"; \ No newline at end of file