diff --git a/.claude/skills/mxl-compile/reference/dsl-spec.md b/.claude/skills/mxl-compile/reference/dsl-spec.md index 595f1c5c..80055230 100644 --- a/.claude/skills/mxl-compile/reference/dsl-spec.md +++ b/.claude/skills/mxl-compile/reference/dsl-spec.md @@ -214,8 +214,8 @@ | `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) | | `param` | нет | — | Параметр заполнения | | `detail` | нет | — | Параметр расшифровки (только с `param`) | -| `text` | нет | — | Статический текст | -| `template` | нет | — | Шаблонный текст с `[Параметр]` | +| `text` | нет | — | Статический текст. Строка или объект `{ ru, en }` — см. ниже | +| `template` | нет | — | Шаблонный текст с `[Параметр]`. Строка или объект, как `text` | ### Содержимое ячейки @@ -225,6 +225,17 @@ - `text` — статический текст; - ничего — пустая ячейка (нужна, например, ради рамки). +### Текст на нескольких языках + +`text` и `template` принимают строку или объект «язык → текст». Строка означает русский; объект даёт по надписи на каждый язык, в порядке ключей. + +```json +{ "col": 1, "text": "Наименование" } +{ "col": 2, "text": { "ru": "Поставщик", "en": "Supplier" } } +``` + +Языки макета объявляются отдельно (`languageSettings` в XML) и по умолчанию ограничены русским; писать текст на языке, которого нет в объявленных, допустимо — так устроено большинство типовых макетов. + ## `rowStyle` — автозаполнение Стиль применяется ко ВСЕЙ ширине строки: позиции без явных ячеек получают тот же стиль. Так в табличных строках получаются сплошные рамки. @@ -244,8 +255,8 @@ XML не возвращается. - рамка с разным стилем у разных сторон; стили линий кроме сплошной; - группировки строк и колонок; - колонтитулы, параметры печати, область печати; -- многоязычные надписи: при разборе берётся первый вариант текста, при генерации язык - всегда `ru`, остальные теряются. +- объявление языков макета: список языков и текущий язык всегда описываются как русский — + сами надписи при этом сохраняются на всех языках. Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому diff после round-trip обычно шире фактической доработки. diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 index 98825528..1eff2ed1 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 @@ -1,4 +1,4 @@ -# mxl-compile v1.23 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# mxl-compile v1.24 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -558,6 +558,29 @@ function Esc-XmlText { return $s.Replace('&','&').Replace('<','<').Replace('>','>') } +# Текст ячейки платформа хранит по элементу на язык. Конвенция ML-значений (та же, что у +# synonym/tooltip/title в метаданных и формах): строка — русский текст, объект — по элементу +# на язык В ПОРЯДКЕ КЛЮЧЕЙ. +function Emit-CellText { + param($value) + $pairs = @() + if ($value -is [System.Collections.IDictionary]) { + foreach ($k in $value.Keys) { $pairs += @{ Lang = "$k"; Text = "$($value[$k])" } } + } elseif ($value -is [System.Management.Automation.PSCustomObject]) { + foreach ($p in $value.PSObject.Properties) { $pairs += @{ Lang = $p.Name; Text = "$($p.Value)" } } + } else { + $pairs += @{ Lang = 'ru'; Text = "$value" } + } + X "`t`t`t`t`t" + foreach ($p in $pairs) { + X "`t`t`t`t`t`t" + X "`t`t`t`t`t`t`t$($p.Lang)" + X "`t`t`t`t`t`t`t$(Esc-XmlText $p.Text)" + X "`t`t`t`t`t`t" + } + X "`t`t`t`t`t" +} + # Helper: determine fillType from cell content # Text НЕ эмитим: платформа его практически не пишет — на выборке корпуса 344 981 текстовая # ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType. Наличие и так означает @@ -1045,23 +1068,9 @@ foreach ($area in $def.areas) { } } - if ($cellInfo.Text) { - X "`t`t`t`t`t" - X "`t`t`t`t`t`t" - X "`t`t`t`t`t`t`tru" - X "`t`t`t`t`t`t`t$(Esc-XmlText $cellInfo.Text)" - X "`t`t`t`t`t`t" - X "`t`t`t`t`t" - } + if ($cellInfo.Text) { Emit-CellText $cellInfo.Text } - if ($cellInfo.Template) { - X "`t`t`t`t`t" - X "`t`t`t`t`t`t" - X "`t`t`t`t`t`t`tru" - X "`t`t`t`t`t`t`t$(Esc-XmlText $cellInfo.Template)" - X "`t`t`t`t`t`t" - X "`t`t`t`t`t" - } + if ($cellInfo.Template) { Emit-CellText $cellInfo.Template } X "`t`t`t`t" X "`t`t`t" diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.py b/.claude/skills/mxl-compile/scripts/mxl-compile.py index ca69b851..8880f1f8 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.py +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-compile v1.23 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# mxl-compile v1.24 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import hashlib @@ -606,6 +606,22 @@ def main(): col_format_map = column_layouts[0]['FormatMap'] # 6c. Helper: determine fillType from cell content + # Текст ячейки платформа хранит по элементу на язык. Конвенция ML-значений (та же, что у + # synonym/tooltip/title в метаданных и формах): строка — русский текст, объект — по элементу + # на язык В ПОРЯДКЕ КЛЮЧЕЙ. + def emit_cell_text(lines, value): + if isinstance(value, dict): + pairs = [(str(k), str(v)) for k, v in value.items()] + else: + pairs = [('ru', str(value))] + lines.append('\t\t\t\t\t') + for lang, content in pairs: + lines.append('\t\t\t\t\t\t') + lines.append(f'\t\t\t\t\t\t\t{lang}') + lines.append(f'\t\t\t\t\t\t\t{esc_xml_text(content)}') + lines.append('\t\t\t\t\t\t') + lines.append('\t\t\t\t\t') + def get_fill_type(cell): """Text НЕ эмитим: платформа его практически не пишет — на выборке корпуса 344 981 текстовая ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType. Наличие @@ -1033,20 +1049,10 @@ def main(): lines.append(f'\t\t\t\t\t{cell_info["Detail"]}') if cell_info['Text']: - lines.append('\t\t\t\t\t') - lines.append('\t\t\t\t\t\t') - lines.append('\t\t\t\t\t\t\tru') - lines.append(f'\t\t\t\t\t\t\t{esc_xml_text(cell_info["Text"])}') - lines.append('\t\t\t\t\t\t') - lines.append('\t\t\t\t\t') + emit_cell_text(lines, cell_info['Text']) if cell_info['Template']: - lines.append('\t\t\t\t\t') - lines.append('\t\t\t\t\t\t') - lines.append('\t\t\t\t\t\t\tru') - lines.append(f'\t\t\t\t\t\t\t{esc_xml_text(cell_info["Template"])}') - lines.append('\t\t\t\t\t\t') - lines.append('\t\t\t\t\t') + emit_cell_text(lines, cell_info['Template']) lines.append('\t\t\t\t') lines.append('\t\t\t') diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 index b7f792dd..999d03e5 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 @@ -1,4 +1,4 @@ -# mxl-decompile v1.5 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.6 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -248,9 +248,22 @@ foreach ($riNode in $root.SelectNodes("d:rowsItem", $ns)) { $dNode = $cContent.SelectSingleNode("d:detailParameter", $ns) if ($dNode) { $detail = $dNode.InnerText } + # Текст ячейки платформа хранит по элементу на язык. Раньше брался ПЕРВЫЙ, и всё + # остальное терялось — в корпусе ERP 98% макетов держат текст и под ru, и под en. + # Конвенция ML-значений: только ru → строка, иначе объект «язык → текст». $text = $null - $tNode = $cContent.SelectSingleNode("d:tl/v8:item/v8:content", $ns) - if ($tNode) { $text = $tNode.InnerText } + $items = $cContent.SelectNodes("d:tl/v8:item", $ns) + if ($items -and $items.Count -gt 0) { + $byLang = [ordered]@{} + foreach ($it in $items) { + $langNode = $it.SelectSingleNode("v8:lang", $ns) + $contentNode = $it.SelectSingleNode("v8:content", $ns) + $lang = if ($langNode) { $langNode.InnerText } else { '' } + $byLang[$lang] = if ($contentNode) { $contentNode.InnerText } else { '' } + } + if ($byLang.Count -eq 1 -and $byLang.Contains('ru')) { $text = $byLang['ru'] } + else { $text = $byLang } + } $cells += @{ Col = $col diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py index a7446066..4a03139f 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-decompile v1.5 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.6 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -428,10 +428,22 @@ def main(): if d_node is not None and d_node.text: detail = d_node.text + # Текст ячейки платформа хранит по элементу на язык. Раньше брался ПЕРВЫЙ, и всё + # остальное терялось — в корпусе ERP 98% макетов держат текст и под ru, и под en. + # Конвенция ML-значений: только ru → строка, иначе объект «язык → текст». text = None - t_node = find(c_content, "d:tl/v8:item/v8:content") - if t_node is not None and t_node.text: - text = t_node.text + items = findall(c_content, "d:tl/v8:item") + if items: + by_lang = OrderedDict() + for it in items: + lang_node = find(it, "v8:lang") + content_node = find(it, "v8:content") + lang = (text_of(lang_node) or '') if lang_node is not None else '' + by_lang[lang] = (text_of(content_node) or '') if content_node is not None else '' + if len(by_lang) == 1 and 'ru' in by_lang: + text = by_lang['ru'] + else: + text = by_lang cells.append({ "Col": col, diff --git a/docs/mxl-dsl-spec.md b/docs/mxl-dsl-spec.md index 9498231c..037ded82 100644 --- a/docs/mxl-dsl-spec.md +++ b/docs/mxl-dsl-spec.md @@ -214,8 +214,8 @@ | `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) | | `param` | нет | — | Параметр заполнения | | `detail` | нет | — | Параметр расшифровки (только с `param`) | -| `text` | нет | — | Статический текст | -| `template` | нет | — | Шаблонный текст с `[Параметр]` | +| `text` | нет | — | Статический текст. Строка или объект `{ ru, en }` — см. ниже | +| `template` | нет | — | Шаблонный текст с `[Параметр]`. Строка или объект, как `text` | ### Содержимое ячейки @@ -225,6 +225,17 @@ - `text` — статический текст; - ничего — пустая ячейка (нужна, например, ради рамки). +### Текст на нескольких языках + +`text` и `template` принимают строку или объект «язык → текст». Строка означает русский; объект даёт по надписи на каждый язык, в порядке ключей. + +```json +{ "col": 1, "text": "Наименование" } +{ "col": 2, "text": { "ru": "Поставщик", "en": "Supplier" } } +``` + +Языки макета объявляются отдельно (`languageSettings` в XML) и по умолчанию ограничены русским; писать текст на языке, которого нет в объявленных, допустимо — так устроено большинство типовых макетов. + ## `rowStyle` — автозаполнение Стиль применяется ко ВСЕЙ ширине строки: позиции без явных ячеек получают тот же стиль. Так в табличных строках получаются сплошные рамки. @@ -244,8 +255,8 @@ XML не возвращается. - рамка с разным стилем у разных сторон; стили линий кроме сплошной; - группировки строк и колонок; - колонтитулы, параметры печати, область печати; -- многоязычные надписи: при разборе берётся первый вариант текста, при генерации язык - всегда `ru`, остальные теряются. +- объявление языков макета: список языков и текущий язык всегда описываются как русский — + сами надписи при этом сохраняются на всех языках. Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому diff после round-trip обычно шире фактической доработки. diff --git a/tests/skills/cases/mxl-compile/snapshots/text-multilang/Template.xml b/tests/skills/cases/mxl-compile/snapshots/text-multilang/Template.xml new file mode 100644 index 00000000..cd9f3567 --- /dev/null +++ b/tests/skills/cases/mxl-compile/snapshots/text-multilang/Template.xml @@ -0,0 +1,86 @@ + + + + ru + ru + + ru + Русский + Русский + + + + 3 + + + 0 + + + 0 + + 1 + + + ru + Только по-русски + + + + + + 1 + + 1 + + + ru + Поставщик + + + en + Supplier + + + + + + 2 + + 2 + + + ru + Итого: [Сумма] + + + en + Total: [Сумма] + + + + + + + true + 1 + 1 + 1 + + Шапка + + Rows + 0 + 0 + -1 + -1 + + + + + 10 + + + 0 + Template + + \ No newline at end of file diff --git a/tests/skills/cases/mxl-compile/text-multilang.json b/tests/skills/cases/mxl-compile/text-multilang.json new file mode 100644 index 00000000..e20a815e --- /dev/null +++ b/tests/skills/cases/mxl-compile/text-multilang.json @@ -0,0 +1,35 @@ +{ + "name": "Многоязычный текст ячейки: строка = ru, объект = по элементу на язык", + "input": { + "columns": 3, + "areas": [ + { + "name": "Шапка", + "rows": [ + { + "cells": [ + { "col": 1, "text": "Только по-русски" }, + { "col": 2, "text": { "ru": "Поставщик", "en": "Supplier" } }, + { "col": 3, "template": { "ru": "Итого: [Сумма]", "en": "Total: [Сумма]" } } + ] + } + ] + } + ] + }, + "params": { + "outputPath": "Template.xml" + }, + "validatePath": "Template.xml", + "expect": { + "files": ["Template.xml"], + "fileContains": { + "file": "Template.xml", + "text": [ + "en", + "Supplier", + "Поставщик" + ] + } + } +}