Локализация — Администрирование¶
Документ описывает администрирование локализации в 1Форме: управление языками в таблице Languages, поштучную и пакетную локализацию сущностей, автоперевод через Azure Translate, строки интерфейса LocalizationResources, встроенную локализацию в контроллерах и SQL-диагностику.
1. Управление языками¶
Таблица Languages. Администратор может добавлять/удалять языки. Ограничения:
- Ровно один язык с
IsDefault=1(уникальный filtered index) - Ровно один язык с
IsInternational=1(уникальный filtered index) LangAliasуникален (unique index)- Удаление языка каскадно удаляет все значения в
LocalizedBusinessObjectValuesиLocalizedDataObjectValues(CASCADE DELETE)
Поля для настройки:
- LangAlias — код языка (rus, eng, de, fr, cn). Он же служит переносимым идентификатором языка между площадками: номера языков в базах разных установок не совпадают, поэтому при переносе данных — например, при импорте почтовых шаблонов — язык определяется по алиасу
- LangDescr — отображаемое имя
- culture — .NET culture code для форматирования дат/чисел (ru-RU, en-US)
- LanguageCode — ISO-код для Azure Translate API
- StatesOnSignPrefix — префикс для подписей на этом языке
Шаблоны системных комментариев. У системных записей ленты («выполнен переход», «запрошена подпись», «изменены плановые трудозатраты» и другие) своя таблица шаблонов — dbo.EventTypeTemplates, где шаблон привязан к событию и языку. В админке локализации они не редактируются: значок локализации к ним не относится, состав шаблонов для языка задаётся миграцией. Поэтому, добавляя новый язык в Languages, нужно убедиться, что шаблоны для него заведены: без них читатель с этим языком получает текст на языке по умолчанию, а не на международном.
Как добавить новый язык (чек-лист)¶
Новый язык появляется в списке выбора, когда закрыты все пять точек. Пример — таджикский (tg-TJ):
- Запись в
Languages— заводится кодовой миграцией, а не через API: контроллер и сервис языков умеют только читать, а прямойINSERTпроходит мимо кеша языков и не воспроизводится на других площадках. Миграция идемпотентна: отметка о накате плюс проверка, что такой культуры ещё нет, а в конце сбрасывается кеш языков. - Код культуры — подбирается так, чтобы файл локали Angular существовал. Загрузчик отбрасывает последний сегмент кода и грузит файл по остатку: для таджикского выбран код
tg-TJ, который приводит кtg.js, а код с письменностью (tg-Cyrl-TJ) запросил быtg-Cyrl.js. Письменность задаёт содержимое ресурсного файла, а не код культуры; перед выбором кода сверьтесь с составом пакета локалей установленной версии Angular. - Ресурсные файлы интерфейса — словари языка: общий, табличного представления и табеля (
tg-TJ,ag-grid-tg-TJ,timesheet-tg-TJ), и их регистрация вbootstrap/resource-providers.ts. Данные локали Angular в репозиторий не кладутся — каталог локалей копируется сборкой. Шрифт проверяется на все глифы языка во всех начертаниях. - Серверные тексты — шаблоны системных комментариев для нового языка и префикс подписей «на подписи» (
StatesOnSignPrefix): без них читатель получает текст на языке по умолчанию. Таджикская миграция этот префикс не заполняет — значение в таблице пустое. - Проверка — переключить интерфейс на новый язык: переведённое показывает перевод, непереведённые места показывают русский текст (это и есть остаток непереведённого), сырого имени ключа не видно. Подписи, которые длиннее русских более чем в полтора раза, проверяются на вёрстку, спорные термины — носителем языка.
2. Локализация сущностей: поштучно и пакетно¶
Рядом с названием каждой локализуемой сущности (категория, статус, ДП, кнопка перехода и т.д.) отображается значок локализации. Такой же значок стоит в профиле пользователя справа от полей «Фамилия», «Имя», «Отчество».
Локализовать можно только уже существующий объект. В окнах создания и копирования категории значка локализации нет — объект ещё не создан; чтобы задать перевод названия новой категории, сначала создайте её, затем откройте переименование и введите перевод там.

По клику на значок открывается окно ввода значения для каждого активного языка системы.

Сохранение: POST app/v2/api/localization/{entityType}/{entityId} с массивом {LanguageId, Value}.
Чтение: GET app/v2/api/localization/{entityType}/{entityId} — возвращает значения для всех языков.
Раздел админки для массовой локализации позволяет выгрузить названия в Excel, перевести и загрузить обратно.
Выгрузка:
- Выбрать язык файла локализации (например, русский)
- Отметить категории или типы сущностей для локализации
- Нажать «Выгрузить файл» → Excel с колонками: EntityType, EntityId, LanguageId, Value
Загрузка:
- Перевести значения в Excel.
LanguageIdдолжен соответствоватьLanguages.IDв БД - Загрузить файл через форму "Загрузить файл"
- Система объединяет переводы для каждой строки
Подробнее: Системные настройки.
3. Локализация текста задач и текстовых ДП¶
Текст задачи и текстовые ДП («Текст», «Большой текст с форматированием / без форматирования») можно вести на нескольких языках. Возможность включается настройкой категории «Локализовать текст задач»; для перевода с форматированием дополнительно нужна настройка «Разрешить HTML в тексте задач», а для ДП — опция «Локализуемый» в расширенных настройках. Локализованное значение задаётся уже в карточке созданной задачи.
По нажатию кнопки «Локализовать» в карточке задачи открывается окно ввода текста для каждого активного языка системы; в самой карточке текст показывается на языке, выбранном в профиле пользователя.

Для обычного текста окно содержит по одному полю на каждый язык.

Если для категории разрешён HTML, поля заменяются редакторами с форматированием — тот же режим применяется и к локализуемым текстовым ДП.

Список языков в окне ограничен активными языками системы (настраивается в окне «Доступные языки» общих настроек приложения).
⚠️ Фильтрация в гриде по локализованным текстовым ДП (Текст, Большой текст с форматированием / без форматирования) недоступна: при включённом флаге «Локализуемый» фильтрующий столбец не отображается. На текстовые ДП без флага локализации ограничение не распространяется.
4. Azure Translate¶
Автоматический перевод при создании/обновлении конфигурационных сущностей.
Как работает
При создании или обновлении сущности: 1. Определяются языки, для которых нет значения 2. Асинхронно запускается перевод недостающих значений 3. Azure Translate API переводит текст на недостающие языки 4. Результат сохраняется
Где применяется
- Создание/переименование категории
- Создание/переименование статуса
- Создание/изменение опции ДП
Лог
Все вызовы Azure Translate логируются в AzureTranslationLog: время, пользователь, исходный и целевой языки, переведённый текст.
Конфигурация
Ключ Azure Translate API хранится в настройках системы. Если ключ не настроен, автоперевод не выполняется — значения остаются только для языка по умолчанию.
Если перевод не появился, потому что сущность удалили во время перевода, ошибки не будет: фоновая задача перечитывает сущность и пропускает запись. Так же пропускается запись, когда переводы сущности удалили осознанно. Объект локализации заново не создаётся, строк без родителя не остаётся.
5. LocalizationResources и встроенная локализация¶
Таблица LocalizationResources содержит статические строки интерфейса (метки, названия, подписи). Двуязычная структура: RussianValue + EnglishValue.
- Кешируется в памяти приложения
- Группировка по
BlockName(напримерEntityNames) - Используется интерфейсом для отображения на выбранном языке
Для добавления/изменения строк: прямая правка в БД или миграции. Отдельной формы в интерфейсе для администрирования нет.
Помимо отдельного Localization API, локализация встроена в контроллеры соответствующих сущностей. При обновлении категории, статуса, ДП через Admin API автоматически обновляются локализованные значения (включая автоперевод через Azure Translate).
Статические строки SPA-интерфейса (подписи, тултипы, пункты меню шапки, боковой панели, окна создания чата, фильтров ленты) локализуются отдельно от таблицы LocalizationResources. Они хранятся во фронтенде — в ресурсных файлах по одному на язык (apps/spa/src/app/resources/*.ts, все 13 языков платформы) — как карты «ключ → значение», сгруппированные по блокам (например, блок common). В отличие от LocalizationResources (двуязычная структура RussianValue/EnglishValue, бэкенд и legacy-контроллеры), эти файлы покрывают все поддерживаемые языки. Поэтому подпись элемента веб-интерфейса правится в соответствующем ресурсном файле SPA, а не в БД.
Подписи логических значений «Да» и «Нет». Там, где значение ДП типа «Флажок» показывается текстом, а не квадратом с галочкой, подпись берётся из тех же ресурсных файлов — блок common, ключи yes и no — и подчиняется общей цепочке отката: нет ключа в файле языка, значение берётся из английского файла, затем из русского. На русском интерфейсе это «Да» и «Нет», на остальных языках — перевод своего языка. Так выводятся колонка ДП «Флажок» в таблице резолюций, снимок задачи и карточка задачи в мобильном приложении. Само значение параметра остаётся машинным — переводится только подпись на экране.
К этим же файлам относятся подсказки значков графа маршрута в дизайнере процессов — «На переходе есть подзадачи», «Подписи: …», «Хранимые процедуры: …», «Есть автоисполнители», «Есть ограничения по дп», «Есть действия с ДП»: их переводы правятся там же, а не в базе.
Экраны карточки задачи. Кроме страниц администрирования ресурсными подписями покрыты экраны карточки задачи: настройка повторений (включая её прежнюю версию), логика и представление карточки, снимок задачи, журнал подписей, история шагов, обновление и постановка задач из CSV, поля дополнительных параметров и их гриды, блок категории и подкатегории, мобильная карточка задачи, встреча в задаче, генерация файлов. Новые ключи интерфейса заводятся сразу в файлы ru-RU и en-US, остальные языки догоняются пакетным переводом словарей. Позиция, которая пользовательским текстом не является — значение, уходящее на сервер, ключ данных, текст в журнале, — остаётся литералом с пометкой о причине; сравнения по русскому тексту в условиях на ключи не переводятся.
Если строка не переведена. Когда ключа нет в ресурсном файле выбранного языка, значение берётся из английского файла, а если и там нет — из русского. Поэтому непереведённая подпись показывается на английском и не пропадает с экрана.
Английская надпись в интерфейсе на другом языке бывает и тогда, когда ключ в файле языка есть, но значение в нём оставлено английским: запасной язык в этом случае не нужен, строка показывается так, как записана. На экране оба случая неотличимы, различить их можно только по ресурсному файлу языка. Отсутствующий ключ исправляется добавлением строки, английское значение — правкой самого значения в файле языка.
Если на месте подписи видно имя ключа. Вместо подписи на экран может попасть само имя ресурсного ключа, например addingAPublication. Так бывает, когда ключа нет ни в одном ресурсном файле и цепочка «язык пользователя → английский → русский» не нашла значения, либо когда ключ записан без префикса блока. Лечится это правкой ресурсных файлов, а не шаблоном страницы. Английская надпись там, где ожидается другой язык, — другой случай, он описан выше.
Если на другом языке видна русская подпись. Русская надпись на интерфейсе другого языка бывает не только из словаря: к ключу в разметке бывает приписано запасное значение — русский текст, который пайп resx подставляет, когда ключ не найден ни в одном ресурсном файле. Запасное значение стоит в разборе последним, а не перед цепочкой: если цепочка «язык пользователя → английский → русский» нашла ключ, показывается найденное значение, и до запасного текста дело не доходит — он подставляется только на пустом результате, а когда запасного значения нет, на экран попадает имя ключа. Поэтому такой дефект выглядит обычной русской строкой и на русском экране незаметен: отсутствующий во всех файлах ключ он не выдаёт, а проявляется на остальных языках. Лечится это правкой кода и словарей — запасное значение убирают, а ключ заводят в ru-RU и en-US; если ключ в файлах есть, а русская подпись всё равно видна, причина в цепочке отката — это случай выше.
Покрытие страниц администрирования. Подписи, названия пунктов меню, диалоги подтверждения и уведомления на страницах отчётов и фильтров отчётов, в дашбордах и их виджетах, в настройках категории и её разделов (блоки дополнительных параметров, кнопки, ресурсы, опросы, тулбары, файлы категорий), в автоматизации на смарт-выражениях и выражениях Lua (включая историю версий и параметры скриптов), на страницах источников данных (настраиваемые пользовательские источники, параметры представлений, мобильные источники данных задач), в группах избранного и настройках шкал календаря, на общих экранах редактора сущностей и очередей, на страницах сервисов и интеграций (список сервисов и форма сервиса, синхронизация с 1С, почтовые ящики — пользовательские, сервисные и категорийные, настройки синхронизации папок ящика), на страницах обслуживания системы (задания и настройки расписаний, кеши, вставки CSS и JS, объекты и параметры таблиц базы данных, денормализация, очереди сообщений, секреты и журнал аудита секретов, рекомендованные и недостающие индексы, импорт документации на диск, системные файлы), на страницах пользователей и доступа (настройки пользователей по умолчанию и карточка пользователя, синхронизация с AD, настройки подписи и плагина ЭП, файловые хранилища, права и источники отчётов, настройки личных задач и интерфейса пользователя), а также в порталах, контейнерах, дизайнере процессов и на стартовой странице администрирования берутся из ресурсных файлов и показываются на языке интерфейса пользователя. На русском подписи прежние, на остальных языках — из файла своего языка. Если подпись здесь показана по-английски, причина та же, что выше: либо ключа нет в файле этого языка, либо значение в нём оставлено английским. Отдельный случай — подпись, которую страница успевает нарисовать до загрузки словаря: она показывается запасным значением из кода, пока словарь не пришёл.
Покрытие общих компонентов. Подписи, подсказки, заголовки колонок, пункты меню и кнопки общих компонентов, которые встречаются на разных экранах платформы, — календаря и просмотра события, досок и канбана, пакетной обработки задач, хлебных крошек категории, диаграммы Ганта и Ганта подзадач, оргдиаграммы, дашбордов, редактора кода, навигации, мобильных карточек и мобильного меню, ресурсной таблицы, конструктора процесса категории, контекстного меню помощника, окна копирования категории, планировщика, селектора категории, подсказок горячих клавиш, пространств, настроек темы оформления, ленты событий и динамического мастера — берутся из ресурсных файлов и показываются на языке интерфейса пользователя; диалоги подтверждения и всплывающие уведомления этих компонентов локализованы так же. На русском подписи совпадают с прежними. Ключи заводятся в ru-RU и en-US, перевод на остальные культуры догоняется пакетным переводом словарей.
Покрытие каркаса веб-приложения. Подписи и подсказки элементов оболочки — левого меню навигации, верхней шапки, панели быстрого доступа, избранного, чата, панели сессий помощника, кнопок-тикеров, всплывающих уведомлений и модальных окон уровня приложения — берутся из тех же ресурсных файлов, блок common. В разметке подпись выводится пайпом resx, а надписи, которые каркас собирает в коде, читаются из снимка словаря: снимок складывает английский словарь блока и словарь языка пользователя так, что значение языка пользователя перекрывает английское. Поэтому ключ, заведённый только в ru-RU и en-US, на остальных языках берёт английское значение, а не остаётся пустой подписью — прямой доступ к словарю сам по себе общий откат не применяет. Значения, которые пользователю не показываются — ключи данных, параметры запросов, текст технических журналов, — остаются литералами с пометкой о причине.
Покрытие экранов мессенджера, чата и встреч. Подписи этих экранов берутся из тех же ресурсных файлов: настройки сообщества и правила его создания, окно добавления подписчиков, список чатов и панель контактов, лента сообщений, панель информации о чате и карточка связанной задачи, диалоги переименования, настройки чата и окно его закрытия, панель встречи и уведомление о встрече, помощник планирования и экран выхода из видеоконференции. Заголовки, подписи кнопок и полей, подсказки навигации по датам, тексты подтверждений и всплывающих уведомлений выводятся по ключам и показываются на языке пользователя; на русском подписи прежние.
Покрытие общих экранов. Подписи общих элементов, встречающихся вне администрирования, — формы единиц времени и размера файла, пресеты периодов в фильтрах («Сегодня», «Текущая неделя», «Последние 3 месяца» и подобные), виджеты портала (фильтр, график, диаграмма Ганта, избранное, контакты, лента социальной сети, кнопка-тикер, меню, новости, поиск задач), создание задачи, мобильные профиль, подписи и настройки, диалоги подтверждения и всплывающие уведомления общих сервисов, экраны загрузки приложения, страницы входа, регистрации и восстановления доступа по логину, профиль пользователя в веб-интерфейсе (просмотр и редактирование, подписи, настройки уведомлений, заместители, токены доступа), страницы Диска, почты и списка писем, просмотра файла, контролов, канбана подкатегории, поиска, резолюций, пространств и журнала событий — берутся из тех же ресурсных файлов и показываются на языке интерфейса пользователя; на русском формулировки прежние. Если подпись на другом языке показана по-английски, причина та же, что выше: либо ключа нет в файле этого языка, либо значение в нём оставлено английским.
Покрытие ленты, публикаций, комментариев и соцсети. Подписи, подсказки, заголовки колонок, пункты меню, кнопки и тексты диалогов подтверждения и уведомлений на экранах ленты, публикаций, комментариев и соцсети берутся из тех же ресурсных файлов и показываются на языке интерфейса пользователя: лента задачи и общая лента вместе с календарным видом, настраиваемыми элементами и виджетами, новостные карточки, публикации и их карточки, список публикаций, комментарии и лента комментариев, редактор комментария с панелью разметки и окном вложений, треды и их списки, опросы (создание, настройка, статистика), реакции на комментарии, а также карточки, панели и диалоги соцсети — профиль, сообщества, подписчики, администраторы, рекомендованные подписчики, подписки и панель подписки. В разметке подпись выводится пайпом resx, а подписи, которые компонент собирает в коде, читаются из словаря. Счётные подписи — число комментариев, голоса опроса, «и ещё N человек» — тоже берутся из словаря: числительное подставляется в значение ключа, а не склеивается строкой. Значения, уходящие на сервер, остаются литералами с пометкой о причине: так оставлены заголовок треда в запросе создания обсуждения и имя файла-вложения. Ключи заводятся в ru-RU и en-US, остальные культуры догоняет пакетный перевод словарей; на русском подписи совпадают с прежними до буквы.
Подписи меню администрирования. Меню админки — статический файл конфигурации: рядом с каждым пунктом хранится и прежний русский текст, и ключ ресурса, а на экран выводится значение ключа, поэтому подписи меню следуют языку интерфейса. Русская подпись остаётся на экране там, где ключа нет: если настройкой пути к панели администрирования подключён внешний файл без ключей, его пункты показываются по записанному в нём тексту.
Страницы «Пользователи и права». Карточка пользователя, включая вкладку «Сервис», настройки группы и редактор прав работают по тем же ресурсным файлам: заголовки вкладок и колонок, подписи кнопок и полей, тексты диалогов подтверждения и всплывающих уведомлений берутся по ключам и показываются на языке администратора. Значения, подставленные в такой текст — имя пользователя, прежний и новый SID, описание учётной записи, название орг. единицы, — вставляются по месту заполнителя при выводе диалога и разметкой не являются.
Страницы сервисов и интеграций. Список сервисов и форма настроек сервиса, настройки синхронизации с 1С, пользовательские, сервисные и категорийные почтовые ящики берут подписи из тех же ресурсных файлов: заголовки колонок гридов, подписи кнопок и полей, пункты контекстного меню, заголовки модальных окон, тексты подтверждений и уведомлений выводятся по ключам на языке администратора. Метки полей формы сервиса хранятся ключами в описании полей и разрешаются при выводе.
Что на этих экранах не переводится. Значения, которые форма получает с сервера — список типов сервисов, очереди и потоки, элементы справочников в выпадающих списках, — переводятся серверными ресурсами, а не файлами SPA, поэтому на английском интерфейсе могут оставаться русскими. Это ожидаемое состояние, а не ошибка.
Страницы публикаций, универсальных шаблонов, индикаторов и сводных разделов. Подписи этих страниц администрирования берутся из тех же ресурсных файлов SPA: заголовки и подписи кнопок, заголовки колонок гридов, подписи полей формы, названия вкладок и тексты диалогов подтверждения удаления выводятся по ключам на языке администратора. Заголовки, собираемые в коде, строятся после получения словаря, поэтому имя ключа на экран не попадает. Тексты сообщений об ошибках приходят с сервера и переводятся серверными ресурсами, а не файлами SPA.
Автоадминка и формы источников данных. Экраны автоадминки — список записей формы, формы источников данных и гриды автоматизации — переведены на те же ресурсные словари: подписи кнопок, заголовки колонок, служебные подсказки, диалоги подтверждения и уведомления выводятся по ключам на языке пользователя, а непереведённый ключ разрешается по общей цепочке отката.
Экраны маршрутов, статусов и подписей. Дизайнер маршрута категории, страница статуса и настройки подписей, включая подписи на переходах и настройки ЭЦП, берут подписи интерфейса из тех же ресурсных файлов: подписи полей, названия вкладок и пунктов списков, тексты диалогов подтверждения и удаления показываются на языке администратора. Поиск по выпадающему списку идёт по видимой подписи, поэтому на нерусском языке в списке набирают перевод, а не русское название.
Экраны оргструктуры, иерархии задач и подзадач маршрутов. Дерево оргструктуры и карточка орг. единицы, страница иерархии задач, настройка подзадач, создаваемых при переходе по маршруту, берут подписи из тех же ресурсных файлов: подписи кнопок и полей, названия вкладок, заголовки колонок гридов, пункты выпадающих списков, тексты диалогов подтверждения и уведомлений выводятся по ключам на языке пользователя. Подстановки в этих текстах — имя и идентификатор орг. единицы, название связанной группы, имя родительской категории — приходят с сервера и подставляются в значение ключа на месте заполнителя, поэтому строка переводится целиком, а заполнители переносятся в перевод без изменений. Заголовок окна редактирования орг. единицы собирается после получения словаря, поэтому имя ключа на экран не попадает.
Отдельно от подписей интерфейса хранятся описания настроек ЭЦП: в базе остаются языконезависимые маркеры вида Attached, Detached, Smart: <выражение>, ExtParam: <ДП>, None, а в таблице настроек они выводятся подписями текущего языка. Значения, записанные прежними версиями клиента по-русски, при выводе распознаются и показываются так же. Подписи этих экранов правятся ресурсными ключами, а не переименованием статусов, переходов или шаблонов подписей.
Локализация подписей автоадминки¶
Названия форм автоадминки, названия их секций (вкладок), заголовки колонок и подсказки под полями переведены на общий контур локализации: связка в dbadmin.AdminCaptionLocalizations ведёт к объекту локализации dbo.LocalizedBusinessObjects, а текст по каждому языку лежит в dbo.LocalizedBusinessObjectValues. Значение выбирается по языку пользователя, и если перевода на этот язык нет — показывается русское. Так подписи читают и дерево форм в админке, и процедура, собирающая конфигурацию формы для грида (dbadmin.DbaQueryFormSchema).
Русское значение контура — эталонное. Оно поддерживается в актуальном состоянии при правке строки в самой админке: сохранение формы, секции или колонки записывает её Caption (для колонки — и Notes) как значение ru-RU, а удаление снимает связки элемента вместе с вложенными — форма уносит свои секции, секция — свои колонки. Английские значения заведены переносом из CaptionAlt/NotesAlt; сохранением строки они не обновляются — перевод на другой язык заводят и правят в самом контуре.
Описания объектов и колонок, пришедшие от самого источника данных, в контур не входят: таблицы описаний (DbaObjects, DbaColumns) зеркалят структуру базы и перезаписываются при её пересборке, поэтому перевод в них не сохранился бы. Переводятся только подписи, заданные на форме.
Качество значения в ресурсном файле¶
Дефектом словаря считается не только пропущенный ключ, но и сам текст значения. Что проверяется при правке:
- Знак препинания в конце. Значение, заканчивающееся знаком, которого нет у того же ключа в соседних культурах, — обычно след строки, скопированной из кода; такой знак убирается. Сверка идёт с
ru-RUиen-US. - Латинская буква внутри кириллического текста. Смешанная запись не видна ни в редакторе, ни в сравнении версий: в подсказке о допустимых символах пароля диапазон «(а-я)» может начинаться латинской
a(U+0061) вместо кириллическойа(U+0430). Различает буквы только код символа. Обратный случай тоже дефект — кириллическая буква в латинском слове или в диапазоне «(a-z)». - Пробел перед знаком препинания. «Сохранить изменения ?» — дефект: перед «?», «!», «:», «;» пробел не ставится. Исключение — значение, где знак сам является перечисляемым символом: в перечне допустимых символов пароля «?» стоит в списке, а не в конце фразы.
- Двойной пробел между словами. Два идущих подряд горизонтальных пробельных символа — обычный, неразрывный (U+00A0), узкий неразрывный (U+202F) — в интерфейсе видны как двойной промежуток. Это же касается стыка с французскими кавычками: между словом и « ставится один обычный пробел, а узкие неразрывные пробелы внутри кавычек сохраняются. Дефект возвращается незаметно — при пакетном переводе и при правке значения вручную, — поэтому однострочные значения словарей всех культур проверяет юнит-тест
resources-no-double-space.spec.ts. - Пробелы по краям значения. Для подписи, которую шаблон склеивает с соседним текстом или с числом, пробел по краю — часть значения, а не оформление: без него надпись слипается. Так, « набор элементов » и « из таблицы » несут пробел с обеих сторон; «Настройка маски », «Шаблон нумератора » и «Я принимаю условия » — в конце, перед следующим фрагментом; суффиксы длительности подзадач на диаграмме Ганта « дн.» и « ч.» — в начале, между числом и единицей. Эталон края — русское значение: пробел стоит там же, где он есть в
ru-RU, а текст между краями при переводе не меняется. Исключение — китайский и японский: в этих языках слова пишутся без пробелов, и значение без краевого пробела для них верно. Там, где шаблон пробела не требует, пробел по краю — дефект: в интерфейсе виден двойной промежуток, поэтому край проверяется по шаблону потребителя, а не по словарю. - Подстановки. Подстановка
${variable}— часть значения, а не оформление: при переводе она переносится без изменений. Потерянная подстановка не косметика — пользователь не видит подставляемого значения, например имени столбца. -
Спецсимволы языка. Узбекская орфография требует знаков U+02BB (в буквах
oиg) и U+02BC (разделитель); обычный апостроф вместо них — ошибка письма, а не вариант начертания. -
Сырая обёртка табличного формата. Значение, залитое в словарь пакетным импортом переводов из таблицы, может сохранить обёртку этого формата: на экране пользователь читает
"EP ""Data da"""вместоEP "Data da". В файле словаря значение хранится обычной строкой: внешние обрамляющие кавычки снимаются, удвоенные кавычки внутри становятся одиночными, а внутренние кавычки экранируются средствами языка, а не удвоением по правилам таблицы. Дефект возвращается с каждым пакетным импортом, поэтому значения всех культур проверяет юнит-тестcsv-quoted-resources.spec.ts: он сверяет число кавычек с тем же ключом вru-RUи считает дефектом только расхождение с источником — закавыченные слова, которых столько же и в русском значении, это смысл фразы, а не обёртка. -
Значение, посимвольно равное английскому. В машинной культуре строка, набранная буква в букву как в
en-US, — не перевод, и запасной язык её не справит: ключ в файле есть, поэтому цепочка отката отдаёт именно это значение. На экране такой случай неотличим от пропущенного ключа — оба показывают английскую подпись, — но лечится иначе: правится само значение в файле культуры. Дефектом он бывает не всегда: в части культур английская форма законна — название продукта, аббревиатура, термин, устоявшийся в словарях этой культуры, — и тогда решение записывается в реестр подтверждений (см. подраздел «Как догоняются машинные культуры»). Очередь таких пар снимаетenv/l10n/english-debt-scan.py; правилоuntranslatedлинтераenv/l10n/lint.pyэтот класс не ловит — оно сверяет значение с русским источником, а не с английским. -
Метка порядка байтов в начале файла. Ресурсный файл словаря хранится в UTF-8 без метки порядка байтов: файл начинается сразу с
export default {. Невидимые байты в начале файла — не содержание словаря: они возвращаются в каждую ветку, трогающую словарь, дают лишний ханк и мешают слиянию, а редакторы, которые пишут метку сами, заносят её обратно при следующей правке файла. Снимая метку, ключи и значения не меняют.
Эти инварианты — знак в конце, латиница внутри кириллицы, пробел перед «?», сохранность подстановки, узбекские апострофы — закреплены юнит-тестом localized-values.spec.ts рядом со словарями: он перечисляет затронутые ключи поимённо и падает, если значение снова испортится. Края значений отдельно сверяет с русским словарём юнит-тест resources-edge-space.spec.ts, двойные пробелы внутри значения — resources-no-double-space.spec.ts.
Серверные словари сообщений¶
Тексты, которые сервер отдаёт пользователю, лежат в 14 словарях Language*.resx: нейтральный файл с английскими значениями и по файлу на каждую культуру — ru-RU, da-DK, de-DE, es-ES, fr-FR, it-IT, ja-JP, kk-KZ, pl-PL, tg-TJ, uz-Cyrl-UZ, uz-Latn-UZ, zh-CN. Клиентские правила качества значения применимы к ним с одной поправкой: подстановка здесь {0}, {1}, а не ${variable}.
| Правило | Как это выглядит на практике |
|---|---|
| Ключ есть во всех словарях | Добавили ключ в ru-RU — он должен появиться и в остальных тринадцати. Пропавшего ключа не бывает: непереведённый словарь отдаёт значение по умолчанию. |
| Непереведённое значение — английское | В da-DK, es-ES, it-IT и других непереведённых языках значение ключа английское. Это норма, а не ошибка. |
В ru-RU значение до символа прежнее |
Русский текст служит эталоном: при правке перевода русский вариант не трогают, а если трогают — только вместе с кодом. |
| Подстановки совпадают во всех культурах | Набор {0}, {1} в значении обязан совпадать с ru-RU. Перевод, потерявший {0}, ломает сообщение молча: номер задачи или имя файла просто исчезают. |
| Без кириллицы в непереведённых значениях | В переводимых словарях кириллицы быть не должно; документированных исключений — восемь. |
| Имя ключа и значение — буквами своего алфавита | Подменённые буквы ищет сторож, см. §7 «Подменённые буквы в ключах и подписях». |
Словари — часть сборки, поэтому перевод попадает на площадку только релизом: правка значения вручную на стенде затирается следующим обновлением.
Как догоняются машинные культуры¶
Одиннадцать машинных словарей (de-DE, es-ES, fr-FR, it-IT, ja-JP, kk-KZ, pl-PL, tg-TJ, da-DK, uz-Latn, zh-CN) ветка не заполняет: правка заводит новый ключ только в ru-RU и en-US, а машинные значения приходят отдельным пакетным догоном. Догон — повторяемый цикл, а не разовая акция: очередь снимается заново по каждой ревизии dev, потому что обычные правки снова приносят ключи лишь в русский и английский словари.
Две половины очереди. Первая — пропуски: ключа в культуре нет, и цепочка отката отдаёт английскую подпись; её снимает env/l10n/catchup-scan.py, а вносит env/l10n/catchup-apply.py — только вставками, без изменения ru-RU и en-US и без перезаписи значения, которое в культуре уже есть. Вторая половина — долг: ключ есть, а значение посимвольно равно en-US, поэтому запасной язык не работает и подпись останется английской навсегда; её снимает env/l10n/english-debt-scan.py, а env/l10n/english-debt-apply.py заменяет только те строки, чьё текущее значение совпадает с английским. Правило untranslated линтера env/l10n/lint.py долг не видит: оно сверяет значение с русским источником, а не с en-US, поэтому одиночное латинское слово при кириллическом русском проходит линтер молча.
Реестр подтверждений. Часть английских значений законна — название продукта, аббревиатура, термин, уже устоявшийся в словарях этой культуры. Такое решение записывается в env/l10n/confirmed-english.tsv парой «культура — ключ», и проверка долга считает пару закрытой; пока записи нет, пара возвращается в очередь на следующем круге. Пример защитимой формы — da-DK navigationAppearanceLogoUpload = 'Upload logo': словарь da-DK системно пишет Upload как глагол (Upload fil, Upload en avatar), то есть форма следует норме самой культуры, а не остаётся недопереводом.
Пакетный импорт вносит собственные дефекты. Обрезанный крайний пробел у разделителя списка: control-dropdown склеивает выбранные значения этим разделителем, и без пробела список рисуется как a,b вместо a, b. Многословная копия английского среди внесённых значений. Ключи вида luaDoc.RESULT.descr, записанные в словаре свойствами в кавычках, должны читаться как строки. Всё это перечисляет поимённо и проверяет спецификация apps/spa/src/app/localization/l10n-dict-catchup.spec.ts рядом со словарями: новый класс дефекта пакетного импорта лечится новым тестом рядом, а не правкой перевода на месте.
Остаток догона — следующая очередь, а не дефект ветки. Прогон закрывает очередь на момент своей базы: ключи, приехавшие в русский словарь позже замера, в панель перевода не попадали и закрываются следующим прогоном. Готовая работа при переносе на свежую базу не патчится — сдвиг строк в словарях ломает патч, и переезд выглядит пустым диффом; восстанавливать догон надо из таблицы результатов панели («ключ, язык, итоговое значение, исходная строка»), применяя её к дереву поимённо.
6. Диагностика локализации¶
Проверка наличия локализации у сущности
-- Business-объект (например категория)
SELECT s.SubcatID, s.Description, s.LocalizedDescriptionId,
bov.LanguageId, bov.Value
FROM Subcategories s
LEFT JOIN LocalizedBusinessObjectValues bov
ON bov.LocalizationId = s.LocalizedDescriptionId
WHERE s.SubcatID = @id
-- Data-объект (например пользователь)
SELECT u.UserID, u.LastName, u.LocalizedLastNameId,
dov.LanguageId, dov.Value
FROM Users u
LEFT JOIN LocalizedDataObjectValues dov
ON dov.LocalizationId = u.LocalizedLastNameId
WHERE u.UserID = @id
Проверка локализации значения текстового ДП
SELECT v.TaskID, v.ExtParamID, v.LocalizedExtParamValueId,
dov.LanguageId, LEFT(dov.Value, 100) AS Value
FROM ExtParamValues v
LEFT JOIN LocalizedDataObjectValues dov
ON dov.LocalizationId = v.LocalizedExtParamValueId
WHERE v.TaskID = @taskId AND v.ExtParamID = @epId
Записи локализации без ссылки (сироты)
SELECT COUNT(*) AS orphan_values_cnt,
SUM(CAST(DATALENGTH(dov.Value) AS bigint)) / 1048576 AS orphan_mb
FROM LocalizedDataObjects lo WITH (NOLOCK)
JOIN LocalizedDataObjectValues dov WITH (NOLOCK) ON dov.LocalizationId = lo.Id
WHERE NOT EXISTS (SELECT 1 FROM ExtParamValues v WITH (NOLOCK) WHERE v.LocalizedExtParamValueId = lo.Id)
AND NOT EXISTS (SELECT 1 FROM Tasks t WITH (NOLOCK)
WHERE t.LocalizedTextId = lo.Id OR t.LocalizedDescriptionId = lo.Id)
AND NOT EXISTS (SELECT 1 FROM Users u WITH (NOLOCK)
WHERE u.LocalizedLastNameId = lo.Id OR u.LocalizedFirstNameId = lo.Id
OR u.LocalizedMiddleNameId = lo.Id)
AND NOT EXISTS (SELECT 1 FROM OrgStructureUnit o WITH (NOLOCK) WHERE o.LocalizedNameId = lo.Id)
Запрос перечисляет все целевые таблицы Data-объектов (ExtParamValues, Tasks, Users, OrgStructureUnit) — без проверки Tasks, Users и OrgStructureUnit в счёт попадали бы живые локализации текстов и описаний задач, имён пользователей и названий подразделений. COUNT(*) считает строки значений (по одной на язык), а не локализации.
Сироты этого класса появляются, если значение текстового ДП переписывалось серверным путём на версиях 2.263.412–2.268 (см. business.md раздел 5): прежние версии значения остаются в таблице без ссылок, занимают место и читаются при полной загрузке LocalizationCache. Удалять найденное вручную нельзя: у LocalizedDataObjectValues связь с LocalizedDataObjects объявлена CASCADE DELETE, а колонки-ссылки в целевых таблицах — ON DELETE SET NULL, поэтому удаление живой записи LocalizedDataObjects уносит её значения и обнуляет ссылку, то есть локализация текста пропадает. Чистка выполняется скриптом разработки.
Проверка языков
SELECT ID, LangAlias, LangDescr, IsDefault, IsInternational, culture, LanguageCode
FROM Languages
ORDER BY ID
Отображаемое имя пользователя не совпадает с данными карточки
Отображаемое имя — в подписях, полях выбора пользователя и результатах поиска — берётся не из Users.*, а из денормализованной таблицы dbo.UserNameModes: строка на каждую пару «режим имени + язык». Поэтому сверяют два уровня — локализованные ФИО пользователя и пересобранные по ним отображаемые имена:
-- локализованные ФИО по языкам
SELECT u.UserID, u.LastName, u.LocalizedLastNameId,
dov.LanguageId, dov.Value
FROM Users u
LEFT JOIN LocalizedDataObjectValues dov
ON dov.LocalizationId = u.LocalizedLastNameId
WHERE u.UserID = @id
-- отображаемые имена по языкам
SELECT UserNameMode, LanguageID, UserID, DisplayName
FROM dbo.UserNameModes
WHERE UserID = @id
ORDER BY LanguageID, UserNameMode
Как читать результат: если для недефолтного языка в LocalizedDataObjectValues лежит прежнее значение, прежним останется и отображаемое имя на этом языке — пересборка имён читает фамилию недефолтного языка именно из локализации. Расхождение в самой локализации даёт ручной перевод, который система не перезаписывает: сохранение карточки обновляет локализованные ФИО для языка оператора и для языков, где значение совпадало с прежними данными пользователя (см. business.md, раздел «Локализованные ФИО пользователя: автокопия и ручной перевод»). Если локализация верна, а DisplayName в dbo.UserNameModes прежние, пересборка имён ещё не выполнялась либо запись кэша отдаётся из прежнего состояния — кэш имён инвалидируется не на всех путях пересборки.
В журнале записи о пропущенном переводе — это не ошибка
Фоновая задача перевода пишет в журнал о пропуске, когда писать нечего: сущность удалена, переводы удалены осознанно или сущность недоступна. Такие записи — ожидаемый результат, а не сбой: обработка завершается штатно, в базе ничего не меняется. Уровень записи — предупреждение, у записи о реальном падении — ошибка, и к ней приложены тип и идентификатор сущности.
| Запись журнала | Когда | Что произошло |
|---|---|---|
Skip background localization translate: entity was deleted |
до или после вызова перевода | Сущность удалили, перевод не выполнялся или его результат не записан |
Skip background localization translate: localization was removed |
перед записью | У сущности обнулили ссылку на объект локализации — переводы удалили осознанно |
Skip localization values merge: LocalizedBusinessObjects {id} is missing |
в момент вставки | Родительская строка исчезла между проверкой и вставкой |
Background localization translate failed |
при любом другом сбое | Настоящее падение: к записи приложены тип и идентификатор сущности, разбирать как обычную ошибку |
Кеш не обновился после прямой записи в БД
Прямая запись в LocalizedXxxObjectValues через SQL не инвалидирует кеш. Решения:
1. Рестарт AppPool
2. Использовать API вместо прямой записи
После очистки кешей подпись на исходном языке, хотя перевод сохранён
Очистка кешей не меняет ни переводы, ни то, что видит пользователь. Если после неё подпись показана на исходном языке, сначала проверьте окно локализации сущности: значение на месте — это дефект чтения, повторно вводить перевод не нужно. Если окно пустое, сверьтесь с таблицей значений по SQL выше: строки есть — потеряна ссылка сущности на перевод, строк нет — перевод не сохранён. Случай заводится задачей.
Нода стартует долго из-за локализации
Полная загрузка кэша локализации читает таблицу значений целиком, поэтому время старта растёт с её объёмом. Оценить объём:
-- объём значений локализованных данных
SELECT COUNT(*) AS rows_total,
SUM(DATALENGTH(Value)) / 1048576.0 AS total_mb,
SUM(CASE WHEN DATALENGTH(Value) > 8000
THEN 1 ELSE 0 END) AS over_8kb,
MAX(DATALENGTH(Value)) / 1024.0 AS max_kb
FROM dbo.LocalizedDataObjectValues
over_8kb — значения, не влезающие в один пакет TDS: именно на них асинхронное чтение деградировало, поэтому полная загрузка выполняется синхронно. Если строк миллионы, а объём измеряется гигабайтами, загрузка на старте занимает минуты; ручное обновление кэшей в этот период не применяется по пределу CacheFullUpdateTimeoutSeconds.
Что заведомо не переводится¶
Перевод не покрывает часть текстов, и это ограничение охвата, а не дефект словаря: строки хранимых процедур и функций, тексты сценариев из каталога, шаблоны писем, серверную генерацию писем и печатных форм, статику оболочки настольного приложения. Подписи библиотеки диаграмм и планировщика закрыты не для всех языков: таджикский и казахский в карту локалей не заведены и получают английский набор. Мобильные клиенты держат собственные наборы языков, не совпадающие с вебом.
7. Подменённые буквы в ключах и подписях¶
Кириллическая и латинская буквы, совпадающие начертанием, на экране неразличимы, но в коде, в ключе настройки и в базе это разные символы. Частые пары: а/a, с/c, е/e, о/o, р/p, х/x, у/y и заглавные к ним. Проверку значений клиентских словарей описывает раздел «Качество значения в ресурсном файле» выше; здесь — серверные ключи и совместимость.
Как это проявляется: ключ перестаёт совпадать с тем, что ищет потребитель, и значение не подставляется, а флаг молча игнорируется; подпись или запись системного перечня не находится поиском по правильному слову, хотя на экране выглядит обычно.
Порядок действий:
- Ключ, имя поля, имя модуля, JSON-свойство — только латиница. Ключ переименовывается сразу во всех языках, в сгенерированных файлах ресурсов и во всех местах вызова: иначе значение перестанет находиться у потребителя, который запрашивает новое имя.
- Если по имени уже есть сохранённые данные — совместимость, а не перевыпуск. Старое написание остаётся читаемым: настройки, сохранённые до исправления, применяются как раньше, а новые сохранения пишутся латинским ключом; выданные ранее лицензии распознаются наравне с новыми, потому что код читает оба написания.
- Текст, лежащий в базе — подписи администрирования, описания системных перечней, локализованные значения — правится новой парной миграцией для обеих баз. Уже накатанную миграцию править нельзя: накатчик выбирает скрипт по ключу, а не по содержимому, поэтому изменённый текст не доедет туда, где скрипт однажды применился.
- За подменой в коде следит тест-страж: новое слово с подменённой буквой вне перечня законных случаев роняет сборку и называет файл, строку и слово. Под проверку попадают и исходники надстройки Outlook, которая поставляется с 1Формой; сторонние библиотеки и результаты сборки надстройки — зависимости (
node_modules/), каталог сборки (dist/) и замок зависимостей (package-lock.json) — из проверки исключены.
8. Перевод базы на Unicode¶
Колонки базовых таблиц MS SQL переводятся на nvarchar версионированными миграциями, и накат управляется рубильником — строкой dbo.SettingsCustom с ключом UnicodeConversion и пустым UserId.
Если накат прошёл при закрытом рубильнике. Скрипт ничего не изменил и записал в журнал наката строку UNICODE-SKIP, а накатчик считает его выполненным и повторно не запускает. База при этом остаётся на ANSI-колонках: чтобы перевести её после открытия рубильника, нужен ручной прогон скриптов перевода. Перед этим стоит убедиться, что рубильник открыт: проверка стоит первой инструкцией каждого пакета скрипта, поэтому части применяются согласованно.
Проверка, что база переведена. Типы колонок:
SELECT OBJECT_NAME(c.object_id) AS TableName, c.name AS ColumnName,
t.name AS TypeName, c.max_length
FROM sys.columns c
JOIN sys.types t ON t.user_type_id = c.user_type_id
WHERE c.name IN ('DisplayName', 'Description', 'Content', 'ExtParamValue',
'ExtParamOldValue', 'ExtParamValueForSort', 'Trigram')
AND t.name NOT IN ('nvarchar', 'nchar')
Пустой результат — все перечисленные колонки уже Unicode. Состояние рубильника:
SELECT [Key], [Value] FROM dbo.SettingsCustom WHERE [Key] = 'UnicodeConversion'
Порядок работ. Сначала переводятся базовые колонки, последним шагом пересоздаются денормализованные таблицы и выставляется флаг IsDenormalizationUC: они хранят копии текстов, поэтому читать уже переведённые значения могут только после того, как колонки-источники стали Unicode.
Пока база не переведена. Текст вне кодовой страницы 1251 заменяется на ? при записи — это касается и значений, попадающих в поиск и денормализацию. Поэтому база без перевода не считается локализованной: подписи интерфейса могут быть переведены, а данные — нет. Колонки PostgreSQL переводить не нужно.
Связанные документы:
- Системные настройки — пакетный перевод