Миллион лет назад, я первый раз попытался использовать штатный механизм управления строковыми ресурсами в Visual Studio: был травмирован, зол и разочарован. С тех пор я видел много иных инструментов для той же задачи, но время как будто остановилось - не меняется ничего. И потому очень рад, что тогда давно сделал собственный инструмент и десятилетия работаю с ним. Кратко расскажу обо всем этом.
Итак, это небольшой компилятор, транслирующий описание ресурсов в бинарное представление, которое используется собственно приложением. Технически, кстати, это - open-source, но находится внутри большого репозитория как инструментальный компонент.
Что он умеет:
Идентификация по целочисленному ключу, заданному с помощью мнемоники. Эта мнемоника транслируется компилятором опять же в целочисленное значение. Что-то вроде такого:
ERR_FILENOTFOUND "Файл не найден"Идентификация строк по явно заданному целочисленному ключу. Скажем, так:
924 "Ошибка компонента супер-библиотека: ничего не могу сделать"
Здесь, видимо, потребуется уточнение зачем такой, прямой как рельса, способ идентификации. Главная причина - внешние компоненты приложения. Вот смотрите, когда мы действуем в зоне ответственности основного приложения, то мнемоническая идентификация отлично работает. Но если у нас есть некая внешняя библиотека, которая поставляет кодировку ошибок и других текстов с явной привязкой к значению, то нам придется расписывать строки именно с такой привязкой.-
Идентификация строк по символьному ключу (просто короткая строка). Например:
@chzn "честный знак"Это - прекрасный способ ссылки на текстовые строки там, где использование мнемоники недоступно. Например, в файле описания диалоговых форм. Ниже будет показан способ применения таких символов здесь же в файле текстовых ресурсов.
Умеет разбивать строки по категориям. Ошибки - здесь, сообщения - там, заголовки диалоговых форм - тут.
Кроме упомянутых пунктов, для большого проекта бывают нужны специальные группы строк, формирующие внутри приложения хэш-таблицы. Зачем это может понадобиться? Например, для разбора и формирования xml- и json-данных. То есть, текстовые наименования тегов и атрибутов определяем в ресурсе, а внутри программы оперируем целочисленными константами.
Это особенно удобно если вы вынужденны работать с тегами в национальном алфавите (nalog.ru и 1с-совместимо, привет!).
В программном коде держать такие строки тоскливо из-за того, что надо помнить в какой кодировке исходный файл.Кроме относительно коротких строк, он умеет работать с длинными текстовыми дополнениями.
Как то так:
@someswitch "Переключатель какой-то"
#{
На самом деле, это - очень важный переключатель. Если вы его переключите, то все пойдет наперекосяк!
#}
Файл строкового ресурса надо переводить на другие языки частью в автоматическом, частью - в ручном режимах. Так как не все строки должны подвергаться переводу, то применяются вспомогательные директивы, позволяющие пропустить перевод одного или нескольких строковых объекта.
Ну и, само собой, комментарии обязательны.
PPHSC_VETIS_DOCTYP_14 "Ветеринарный сертификат РФ на вывоз продукции на территорию стран ТС" // Текст не менять - точное соответствие!
А еще есть опция, позволяющая в определении строк ссылаться на другие строки в этом же ресурсе. Вот пример (строка с символом
@rejectedопределена где-то в этом же файле):
@styloqdocstatus_fbillflagdeclined "Использовать признак документа '@{rejected}'"
Есть еще нетривиальная фича: скомпилировать этот ресурс в бинарный файл и использовать результат в разных проектах. На c++, java и может еще каких-то. Мы применяем эту возможность чтобы не плодить дублируемые сущности для разных проектов. В том числе для Android.
При компиляции ресурса для строк с мнемониками автоматически формируются программные определения. Чего бы не хотелось, так это изменения значений идентификаторов если мы вздумаем вставить новую строку в середину списка, или удалить существующую. Это делается так: при компиляции одновременно с бинарным представлением формируется файл соответствий мнемоник числовым значениям. Этот файл, так же как и исходный модуль, сохраняется в репозитории. При следующей сборке компилятор смотрит на этот файл и, если находит соответствие, то присваивает встреченной мнемонике ранее определенный идентификатор.
Пример файла описания
Пример, разумеется, игрушечный. Реальный модуль содержит почти 20 тысяч строк.
[1]
PPERR_INVNFPARAMBUF "@{err_internal}: недопустимый размер буфера параметров пакетного задания"
PPERR_CMDSEL_EXP_SELECT "@{err_incmd}: ожидается SELECT"
PPERR_CMDSEL_EXP_OBJTYPE "@{err_incmd}: ожидается тип объекта"
PPERR_CMDSEL_EXP_BY "@{err_incmd}: ожидается BY"
PPERR_CMDSEL_EXP_CRITERION "@{err_incmd}: ожидается критерий"
[2]
PPTXT_TODO_PRIOR "1,@{todoprior_highest};2,@{todoprior_high};3,@{todoprior_normal};4,@{todoprior_low};5,@{todoprior_lowest}"
PPTXT_LOG_UNEQTRFRBILLDATE "Дата строки документа не соответствует дате документа (%s)"
[25]
700 /!/ "AMQP error: Unknown AMQP error (%s)"
701 /!/ "AMQP error: Incorrect or corrupt data was received from the broker. This is a protocol error (%s)"
[100]
@position_left "Слева"
@position_right "Справа"
@direction_left "Налево"
@direction_right "Направо"
@direction_up "Вверх"
@direction_down "Вниз"
@direction_forward "Вперед"
@direction_backward "Назад"
@cancelinput "Отмена" // В смысле UI отмена ввода
[124:hash]
#!{ // Директива "не переводи!"
PPHSC_RU_FILE "Файл"
PPHSC_RU_IDFILE "ИдФайл"
PPHSC_RU_VERPROG "ВерсПрог"
PPHSC_RU_VERFORM "ВерсФорм"
PPHSC_RU_VERFORM2 "ВерсияФормата" // @v11.9.5 SBIS
PPHSC_RU_FORMAT "Формат" // @v11.9.5 SBIS
PPHSC_RU_REPFORM "ФормаОтч"
#!}
Где это все?
Как я упоминал выше, описанная техника работы с текстовыми ресурсами - часть большого проекта. Вот его адрес на: https://github.com/papyrussolution/OpenPapyrus.
Компилятор называется назамысловато: sc2c.
Вопросы, замечания, молчаливое использование - все приветствуется!
Комментарии (6)

Apoheliy
12.01.2026 15:02Сначала хотел плавно написать несколько аргументов, но сегодня первый рабочий день и всё такое ...
Поэтому зайдём с козырей (и, да прости меня автор): я, конечно, понимаю, что Windows до сих пор занимает на десктопе +-67%. Но делать утилиту-компилятор нацеленную только на Windows! - по-моему, это какой-то "привет из нулевых". Не А[А[А]]-игра, а утилита! И сама эта утилита в виде .exe-файла прямо выложена в репозитории: типа пользуйтесь! Что, реально?
Даже при наличии исходников (может они и есть, но поискал по диагонали - не нашёл) вопрос использования не всегда однозначен. А так ...
И да, прошу прощения за эмоции.
С другой стороны, может лучше написать статью про пользование самим продуктом? Он хотя и виндозний, но это законченная штука - так что может и нормально будет.

antonsobolev Автор
12.01.2026 15:02Я, вероятно, не смог четко донести смысл текста. Дело не в Windows и не в самой утилитке. Я попытался предложить концепцию управления ресурсами строк (методы идентификации, подходы к переводу и т.д.).
Ссылка на репозиторий дана только в качестве пруфа.
Сам исходник не большой. Вот ссылка на него: https://github.com/papyrussolution/OpenPapyrus/blob/master/Src/PPLib/STRSTORE.CPP
berez
Мне кажется, статье не хватает пары вещей:
Первое и самое главное: примеры использования. На простых примерах возможности вашей системы станут гораздо понятнее, чем просто описание "можно вот это, а еще вот это". К тому же примеры подразумевают проход по всему процессу: от написания текста программы и файла описания строк к ней и до сборки и запуска тестового бинаря. Серия постепенно усложняющихся примеров отлично бы проиллюстрировала не только возможности, но и предполагаемый сценарий использования системы.
Ну и второе. Это, конечно, косметика, но хотелось бы более подробное сравнение с современными системами управления строковыми ресурсами. Чем ваша система лучше того же GNU gettext? А то сейчас статья выглядит так: "посмотрел, как реализовано в visual studio, впал в шок, написал своё. С тех пор на другие системы не смотрел".
antonsobolev Автор
Насчет примеров использования, да - я должен был это включить, но не включил. Исправляюсь:
По поводу GNU gettext. Я долго смотрел на него и примерялся. Его популярность восхищает. Однако ж, его основное назначение - организация перевода на другие языки, а не управление текстовыми ресурсами. И еще - идентификация по тексту, примененному в исходно коде очень уж зыбкая.
berez
Ну не совсем. Хотелось бы увидеть полный пример работы с системой, а не только невнятную строку загрузки текста в некий буфер (что это за буфер, кстати? std::string? std::vector<char>? char[MAX_STRING_BUF]?).
Как составлять файл описаний, как его транслировать в бинарное представление, как он связывается с основной программой, как он в основной программе подгружается (если вообще подгружается), и т.п. Хотя бы для С++.
А как у вас организован перевод на другие языки? Об этом в статье нет никакой конкретики.
И кстати, а для чего вообще нужно "управление текстовыми ресурсами", если не для локализации?
С одной стороны - да. С другой - если в исходном коде поменяли какой-то текст, то и в переводах его тоже, скорее всего, нужно менять.
antonsobolev Автор
Ух. Это - скорее будет документация, а не ознакомительный текст. todo мне.