Перевод проекта
Перевод исходников на английские написания: что отвечает по данным платформы, что по словарю проекта, как считается покрытие и по какому условию падает CI.
1С:Элемент двуязычен: у каждого ключевого слова, ключа метаданных, типа, члена и значения
перечисления есть английское написание, и проект, написанный ими, компилируется так же, как
русский. xbsl translate переписывает проект целиком: платформенную половину – по извлечённым
данным платформы, собственную половину проекта – по словарю, который ведёт
команда.
xbsl translate e1c/app # только отчёт: покрытие и что осталось
xbsl translate e1c/app --coverage # плюс разбивка по объектам метаданных
xbsl translate e1c/app --missing gaps.yaml # непереведённый остаток заготовкой словаря
xbsl translate e1c/app --out build/app-en # записать переведённое дерево
xbsl translate e1c/app --out build/app-en --strict # ненулевой выход, пока не полно
Что чем переводится
Платформенная половина – из данных, никогда не переводом. Ключевые слова берут форму того же
регистра (Если -> If, если -> if); ключ yaml – английское написание, объявленное его
классом метамодели, поэтому одно и то же слово в разных узлах может отличаться; значение
перечисления ищется внутри своего перечисления (глобально одно русское слово отвечает нескольким
английским); выражение типа сохраняет форму и фасеты (.Ссылка -> .Reference) – и в yaml, и в
коде, всюду, где парсер читает тип (параметр, объявление, конструктор, приведение, аргумент
типа), а то же слово после точки в другом месте – член: член-ссылка у приёмника, который держит
фасет объекта проекта или чья цепочка дальше загружает запись, – тоже слово фасета
(Reference), а свойство-ссылка надписи или картинки – Link; значение свойства с
объединённым типом, совпадающее с членом объединения (Авто в Авто|Число), – этот член в
написании платформенной пары типов; внутри блоков
Запрос{ ... } отвечает словарь языка запросов, а не общий. Имя, которого данные не знают,
остаётся как написано и попадает в отчёт пробелом данных – транслятор не угадывает.
Половина проекта – из словаря. Всё, что проект назвал сам: объекты, методы, реквизиты,
компоненты форм, ключи словарей, файлы ресурсов, значение по умолчанию перечисления – голое или
квалифицированное своим перечислением (Состояния.Открыт -> States.Open), – и каждая
кириллическая строка комментария переводятся людьми. Три плана:
version: 1
language: en
# tokens: идентификатор целиком -> идентификатор целиком (файл ресурса – основой имени)
tokens:
Задачи: Tasks
Значок: Icon
# phrases: строка комментария -> её перевод
phrases:
"Задача помечается выполненной.": "The task is marked done."
Отступ – четыре пробела, как пишет сам инструмент. Свой отступ словарь тоже сохраняет: писатель
копирует его у соседних записей секции, поэтому файл, заведённый двумя пробелами, после правки из
панели или --set остаётся валидным.
Третий план – literals – про строковые литералы кода:
literals:
"Файл не загружен": "The file was not uploaded"
"Не заполнено поле \"Наименование\"": "The \"Name\" field is empty"
"Не разобрано тело: %{Описание}": "Could not parse the body: %{Описание}"
Литерал – данные, и переводчик их не угадывает: он заменяет ровно то, что команда перечислила.
Ключ и значение пишутся так, как текст стоит в исходнике МЕЖДУ КАВЫЧКАМИ – с тем же
экранированием, что и в коде; значение, которое не является правильным телом литерала, словарь
отвергает при загрузке. Интерполяция в значении пишется как в исходнике: имя внутри неё переведёт
сам движок. Литерал внутри Запрос{}, Образец{} и других разрешимых литералов не трогается –
там это код, а не данные.
Два места, где текст и код перемешаны, план литералов обслуживает особо. Имя именованной группы
образца ((?<Имя>...)) – это имя проекта: код читает группу обратно по нему (Группа("Имя")), и
обе стороны берут написание из ОДНОГО источника – сначала план литералов, затем обычное разрешение
имени. Разойдись они, вызов спросил бы группу, которой образец не объявлял. Шаблон представления
вида события журнала – проза с выражениями внутри: выражения переименовываются как имена, а сам
текст берётся из плана литералов по всему значению; чего план не назвал, попадает в отчёт пропусков,
а не остаётся русским молча.
Суффиксы литерала длительности в коде переезжают на английские написания сами
(300мс -> 300ms, 2д14ч30м5с6мс -> 2d14h30m5s6ms): состав русских суффиксов задан
справкой типа, английский подтверждён компилятором платформы. Число, склеенное с любыми
другими буквами, не трогается.
Тем же планом идёт всякое значение yaml, которое метамодель объявляет локализуемым текстом
(Localizable): представления команд, прав доступа и перечислений человек читает на странице,
поэтому они либо названы записью целиком, либо предъявлены пропуском. Исключение – свойство
Описание: это документация разработчика, она остаётся данными и в пропуски не попадает.
Имена целиком, а не по словам: порядок слов английского имени обратный русскому, а части русского
имени склоняются – склейка пословных переводов даёт кальку. Комментарии переводятся построчно:
правка соседней строки не обесценивает запись, а одна запись обслуживает все повторы. Готовый блок
комментария переразбивается по ширине проекта – той же, которой пользуется правило
style/line-length: перевод, ставший длиннее исходника, иначе вылезал бы за предел. Рамки и
разделители, списки, таблицы и образцы кода, а также строки, длинные уже в исходнике, остаются как
были.
Словарь – каталог yaml-файлов (или один файл) с именем xbsl-translation, который ищется рядом с
проектом и выше. Наполнение – это положить заполненную заготовку рядом с остальными; два файла,
разошедшиеся в одном ключе, отвергаются при загрузке.
Квалифицированная запись (Словарь.Ключ: SignIn) действует только внутри одного пространства
имён: ключу словаря локализованных строк может быть нужно написание, которого тому же слову
нельзя дать в коде.
Имена, объявленные проектом, принадлежат проекту
Слово, которое знает и платформенный словарь, может быть тем, как проект назвал своё: значение перечисления, реквизит, метод, ключ словаря. На такие имена отвечает ТОЛЬКО словарь проекта. Без этого гейта обращение уезжало бы на английское написание, пока объявление ждёт записи в словаре, – yaml всё ещё объявлял бы русское значение, а модуль уже звал английское, и сборка отвергла бы дерево. С гейтом обе половины переезжают вместе – или вместе ждут одной записи.
Исключения остаются за платформой: встроенные элементы коллекций, которые диспетчеризуются по имени (стандартные код, наименование и владелец), и фасет после точки в выражении типа.
Что не трогается
Данные. Подписи, описания и любой другой текст, который читает пользователь, остаются как
написаны – их переводит штатный механизм локализации, и переведённый проект несёт те же словари.
Строковые литералы тоже остаются, кроме КОДА внутри их интерполяций: он разбирается заново и
переводится как обычный код. Ид не меняются: переведённое дерево – тот же проект, а не его
копия.
Строковый литерал, совпавший с переименованным именем, попадает в предупреждения: метод, который зовут по имени из строки, ломается молча, если переименовано только объявление.
Второе предупреждение того же рода – literal-data-value: литерал, совпавший со ЗНАЧЕНИЕМ из
json-ресурса проекта, который запись плана литералов сдвинула. Такой литерал обычно сравнивается
с этими данными (разбор начального заполнения), а данные перевод не трогает – после сдвига
сравнение молча пустеет. Если литерал и правда данные, запись со значением, РАВНЫМ ключу, помечает
это явно: покрытие засчитано, текст не движется, предупреждения нет. Отчёт печатает предупреждения
списком – файл, строка, вид и текст.
Исключений два, и оба – имя, записанное ВТОРОЙ раз, вне кода.
Ключи json-ресурсов проекта, называющие поле его структуры. Структура читает json по имени поля, поэтому такой ключ и есть то же имя, записанное ещё раз: переименовать поле и оставить ключ значит не связать ничего. Молча – настройки чтения пропускают неизвестное свойство и инициализируют отсутствующее поле, поэтому проект компилируется, применяется и стартует с пустыми данными. Такие ключи переезжают тем же словарём, что и поля; значения и ключи, которых не объявляет ни одна структура (карта по содержимому, внешний контракт), остаются как написаны. Число переименованных ключей стоит в сводке прогона.
Литерал, который записывает ПУТЬ РЕСУРСА ("Значки/%Код.svg"). Дерево переименовывает файлы
и каталоги ресурсов, и путь обязан следовать за ними, иначе платформа ресурс не находит. Литералом
пути считается только то, что им и выглядит: оканчивается известным суффиксом ресурса, а каждый
сегмент читается как имя файла. Регулярное выражение с косыми чертами и именованными группами под
это не подходит и остаётся данными.
Словари локализации переворачиваются
Проект, у которого целевой язык уже лежит в секциях локализации, получает эти значения БАЗОВЫМИ
(с переведёнными ключами), исходные значения уезжают в Локализация/<Код>/, а язык по умолчанию
и язык разработки в дескрипторе следуют за этим. Ключ, у которого значения на целевом языке нет,
сохраняет исходное и попадает в отчёт.
Покрытие и пробелы
--coverage печатает долю словаря по каждому объекту метаданных (семейство yaml/xbsl с общей
основой имени) и по проекту целиком. --missing пишет остаток заготовкой словаря: записи по
убыванию частоты, у каждой – число вхождений и первое место, значения пустые и готовы к
заполнению.
Три счётчика разведены намеренно: покрытие СЛОВАРЯ (число, которое команда доводит до 100%), пробелы ДАННЫХ платформы (их не заклеивают записью в словаре) и кириллические скаляры, оставленные данными (перечислены, чтобы ревизор подтвердил: это и правда данные).
Словарь ищется рядом с проектом и выше – корень без словаря отвергается с перечнем мест
поиска и словарём, найденным ниже корня, если он там есть; --dictionary называет его явно
(файл или каталог), а
--target – файл, в который лягут НОВЫЕ записи (по умолчанию 090-manual.yaml), а
--comment – заголовок, с которым такой файл создаётся: там и место сказать, чему посвящена
порция. --format json отдаёт весь отчёт машине. --no-localization-swap оставляет словари локализации как есть – это
для проекта, который переводит исходники, но раскладку языков менять не хочет.
--strict даёт ненулевой выход, пока покрытие неполно или найдены проблемы, – это и нужно джобе
CI перед выкладкой переведённой сборки. КОЛЛИЗИЯ имён – такая проблема: два разных имени одного
пространства, переведённые в одно слово, ломают сборку (платформа отвергает повтор имени), и
увидеть это может только транслятор.
В редакторе
Правило conventions/missing-translation (info, выключено по умолчанию, область – проект)
показывает те же пробелы на их местах: имя или строка комментария, которых нет в словаре, – одна
находка на первое вхождение в файле. Оно молчит, пока словарь не найден, поэтому говорит только в
проекте, который переводит исходники. Включается --enable conventions/missing-translation (в
редакторе – настройка Linter: Enable).
Находка несёт с собой ключ словаря, его вид и подсказку, поэтому лампочка предлагает записать перевод не выходя из файла: “Перевести как …” ставит платформенное написание одним щелчком, “Перевести …” спрашивает слово, а третьим действием открывается таблица словаря с отбором по этому ключу. После записи проект проверяется заново – перезапуск не нужен.
Таблица словаря (команда “XBSL: словарь перевода”) показывает записи рядом с тем, что
исходники ещё не покрывают: вид, ключ, перевод, число вхождений, первое место и файл словаря –
каждая запись на двух строках, поле перевода растянуто под остальным во всю ширину строки. Поле
правится на месте, строка поиска и переключатель “только незаполненные” сужают таблицу, а
подсказка – сперва платформенное написание, иначе догадка внешнего сервиса – стоит серым внутри
пустого поля и подставляется щелчком или клавишей Enter. Таблица читает --table – записи,
пропуски и покрытие за один проход по проекту – и пишет --set; после записи ячейки проект
заново не обходится: перечитывается только словарь, а счётчики шапки сдвигаются на то, что
изменила эта правка. Точные числа возвращает кнопка “Перечитать”. Панели нужен движок 0.72.0
или новее – --suggest,
запрос машинного перевода за кнопкой предложений, появился только там. Кнопка “Запросить
машинный перевод” в этой же панели заполняет пустые поля предложениями внешнего сервиса –
подробности в разделе Машинный перевод ниже.
Наполнение словаря из инструментов
Словарь большого проекта – тысячи записей, и вычитывать эти файлы ради одного слова долго и чревато ошибками. Поэтому все поверхности работают СТРАНИЦАМИ поверх одного ядра движка.
CLI отвечает на те же вопросы, что задаёт панель:
xbsl translate e1c/app --gaps --kind token --limit 20 # чего не хватает, по убыванию частоты
xbsl translate e1c/app --entries --filter Задач # что словарь уже говорит
xbsl translate e1c/app --table --limit 0 # всё сразу: записи, пропуски, сводка
xbsl translate e1c/app --set правки.yaml # применить пачку из файла (ниже)
xbsl translate e1c/app --unused # пары, которых проект уже не использует
--table отвечает на три вопроса разом, одним проходом: он для того и заведён, что таблица
редактора спрашивает именно их – порознь это два одинаковых обхода исходников в двух процессах
и третье чтение того же словаря.
--set принимает пачку ФАЙЛОМ в любом из двух форматов: yaml самого словаря (секции
tokens/phrases/literals, то же квотирование, что в файлах словаря; пустое значение
снимает запись) либо список JSON [{key, value, kind}], который отдают скрипты. Пачка
на сотни записей пишется так же, как сам словарь, а не инлайновым JSON.
--unused отвечает на обратный --gaps вопрос: не что нужно проекту, а чего нет в словаре,
а что словарь всё ещё говорит, чего в проекте уже нет. Удаление кода оставляет за собой его
имена и строки комментариев, и сказать об этом больше некому: --strict судит НЕПОКРЫТОЕ,
а --entries показывает, где пара объявлена, а не используется ли она. --prune снимает ровно
те строки, которые только что перечислил, поэтому --kind, --filter и страница действуют и на
снятие; страницу, урезанную --limit, отчёт называет отдельно – снимать “всё”, глядя на
пятьдесят строк из трёх тысяч, флаг только выглядит.
Чтение текстовое, и важна сторона его ошибки: имя, которое встречается ещё и в прозе, может быть
сочтено используемым – это всего лишь оставит пару на месте, – но ЖИВАЯ пара сиротой не
называется. Квалифицированный ключ (<Владелец>.<Имя>) судится по обоим концам: в исходниках
они написаны порознь, и чтение точечного текста как одного имени объявило бы сиротой каждую
такую пару.
--gaps показывает частоту, первые места употребления и suggestion – собственное написание
платформы, если оно у неё есть. Подсказка – именно подсказка: имя, объявленное проектом, может
намеренно требовать другого слова, а ВНУТРЕННЕЕ имя платформы (класс метаданных вроде
CodeAttrMd) не предлагается вовсе.
Инструменты MCP – те же четыре, для агента, который наполняет словарь:
translate_status– покрытие и остаток, дешёвая проверка перед любым решением;translate_gaps– непереведённое страницами (kind,filter,limit,offset), в ответе – прочитанныйdictionary;compactотдаёт по строке только{key, kind, count}– форму рабочего списка переводчика, когда полные строки не влезают в ответ;translate_entries– что уже в словаре, с файлом и строкой каждой записи, чтобы новое слово согласовывалось с принятыми;translate_set– запись: добавить, исправить на месте или снять, обнулив значение;edits_file– пачка файлом в тех же двух форматах, что читает--set.
Новая запись попадает в 090-manual.yaml (или в файл из target), а уже существующая
правится там, где живёт: писатель не плодит дублей, а дубль с другим значением словарь
отвергает при загрузке.
Машинный перевод
Заполнить большой остаток вручную долго; --suggest добивает недостающее через внешний
переводческий сервис – ПРЕДЛОЖЕНИЯМИ, а не записью: словарь не меняется, пока предложение не
принято (в консоли – флагом --suggest-out, в редакторе – щелчком или клавишей Enter по
подсказке в таблице).
xbsl translate e1c/app --suggest # отчёт: что предложил сервис
xbsl translate e1c/app --suggest --provider yandex # выбор сервиса явно
xbsl translate e1c/app --suggest --suggest-out 080-machine.yaml # записать план рядом со словарём
xbsl translate e1c/app --suggest --plans tokens # добивать только имена
Прогон всегда идёт по всему проекту: ограничить его нечем, остановить на полпути нельзя, заранее оценить объём тоже нечем – а каждая отправленная порция это платный запрос к сервису.
--suggest-out называет файл плана, в который лягут предложенные записи: каталог из пути
отбрасывается, берётся только имя файла, и оно ложится внутрь каталога словаря. Если словарь – это
ОДИН ФАЙЛ, отдельного плана не выходит: записи ложатся в него же.
Без --provider движок берёт единственный настроенный сервис. Если не настроен ни один, отказ
называет переменные окружения, которых не хватает. Если настроено больше одного, отказ
перечисляет сами сервисы (google, yandex) и просит выбрать флагом --provider – ни в одном
из двух случаев движок не гадает молча.
Два сервиса, и они не взаимозаменяемы.
- Yandex Translate – авторизация ключом сервисного аккаунта И идентификатором каталога (folder), нужны оба; порция – до 10000 символов за запрос; понимает глоссарий – список терминов проекта уходит вместе с текстом самого запроса.
- Google Translate – авторизация одним ключом; порция – до 5000 символов; глоссария в его API нет вовсе – написание термина накладывается уже после ответа, когда движок собирает имя из вернувшейся прозы.
Сколько бы ни позволял сервис, в одном запросе едет не больше 100 текстов – собственный предел движка, взятый с запасом. Порция, отвергнутая целиком за размер, стоит ровно столько же, сколько принятая, а по одной лишь сумме длин в один запрос уехало бы шестьсот однословных имён.
Ключи – в окружении, никогда в командной строке или в файле настроек. Три переменные, поимённые под каждый сервис:
XBSL_TRANSLATE_YANDEX_KEY,XBSL_TRANSLATE_YANDEX_FOLDER– ключ и каталог Yandex, нужны оба;XBSL_TRANSLATE_GOOGLE_KEY– ключ Google, один.
В редакторе переменные окружения заводить не нужно: команда XBSL: Задать ключ машинного
перевода (xbsl.translate.setKey) спрашивает, какой из трёх завести, и кладёт значение в
защищённое хранилище (SecretStorage) расширения – оно передаёт его движку в окружении самого
запуска --suggest, а не в настройке и не в командной строке. Настройка
xbsl.translation.provider выбирает сервис, если настроены оба сразу; сам ключ ею не задаётся.
Кэш – файл machine-cache.json рядом со словарём (в каталоге xbsl-translation, если
словарь – каталог, иначе рядом с единственным файлом словаря). В нём хранится СЫРОЙ ответ
сервиса по паре (сервис, язык, отпечаток глоссария, текст) – не готовое имя: правило сборки
имени и список терминов ещё изменятся, а переспрашивать сервис ради того же текста незачем.
Формат – JSON, не yaml: словарь ищет *.yaml рекурсивно и принял бы файл кэша за ещё один план
перевода с повторяющимися ключами. Запись остаётся в кэше независимо от того, приняли
предложение в словарь или нет, – повторный --suggest не платит за один и тот же текст дважды,
даже если прошлый ответ никто не принял.
Секция terms словаря – короткий список “русский термин -> английское написание”, не план
перевода и не часть покрытия. Из неё берутся сразу две вещи: пары глоссария, которые уходят в
запрос вместе с текстом (это умеет только Yandex), и написание для СБОРКИ ИМЕНИ. Вторая половина
работает по ОТВЕТУ: собирая идентификатор, движок сверяет каждое английское слово ответа со
списком терминов без учёта регистра и ставит словарное написание, где бы слово ни стояло во фразе.
Русские формы слова при этом не разбираются вовсе, а термин, вернувшийся другим английским словом
(множественным числом, синонимом), не опознаётся и остаётся как ответил сервис. На перевод строки
комментария (phrases) термины не влияют – комментарий остаётся тем, что ответил сервис,
целиком.
Имена собираются детерминированно, а не сервисом. Сервис отвечает прозой (“Site address”),
а плану tokens нужен идентификатор: движок убирает служебные слова (a, the, of…),
подставляет написание термина, где оно есть, и склеивает остаток – каждое слово с большой буквы.
Имя, уже занятое другим ключом словаря, или прозу, из которой не вышло собрать идентификатор,
--suggest отклоняет с названной причиной – сервис не угадывает и не переписывает поверх.
Литералы в сеть не ходят вовсе. --suggest посылает сервису только пробелы tokens и
phrases (имена и строки комментариев целиком); строковый литерал заполняется отдельно и
только тогда, когда его текст совпадает ЦЕЛИКОМ с уже принятым именем – локально, без единого
запроса к сервису.
Что уходит наружу – без утайки. Сервис получает только текст пробела: имя как оно написано в проекте или строку комментария целиком. Ни путь файла, ни код вокруг, ни остальной проект сервис не видит. Без ключа команда не делает ни одного запроса ни к Yandex, ни к Google – сразу отказывает и называет переменную, которой не хватает.
Итог печатается тем же способом, что и у --set: cached – сколько ответов взято из кэша,
requested – сколько запрошено заново, refused – сколько отклонено (с причиной у каждого).
В редакторе те же три числа остаются в собственной строке сводки панели до следующего прогона, а не только в сообщении строки состояния, которое гаснет через несколько секунд; наведение на эту строку называет причину каждого отказа. Когда спрашивать было решительно не о чем, строка говорит об этом словами вместо трёх нулей, а когда все предложения дали локальные совпадения литералов без единого запроса, сказано и это.