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",
+ "Поставщик"
+ ]
+ }
+ }
+}