Текст и локализация
Engine определяет формат исходных .fotxt, поля $Text прототипов, запекание
языков, поиск текста и встроенные цветовые теги. Точные правила —
справочник и
JSON-модель. Форматы шрифтов
описывают растровые дескрипторы, слоты, глифы, измерение, переносы и флаги рендерера.
Проект определяет языки, каталоги пакетов, ключи, переводы и обработку шаблонов;
контракт Engine не зависит от пакетов или лексем конкретной игры.
Модель текстового пакета
Каждое текстовое значение хранится под ключом TextPackKey:
Collection + Key1 + Key2 + Key3
Collection имеет тип TextPackName, а остальные поля являются значениями
hstring. Язык намеренно не входит в ключ. Поэтому один ключ может
существовать в запечённом пакете каждого языка.
Вектор, отсортированный по полному ключу, хранит соседние варианты. Успешная
загрузка, слияние и исправление fallback восстанавливают порядок. Чтение
проверяет его и выполняет двоичный поиск без исправления разделяемого состояния.
Game.GetTextCount(key) возвращает количество вариантов,
Game.GetText(key, index) выбирает вариант по индексу с нуля.
Нативный TextPack::LoadFromBinaryData принимает const_span<uint8_t> по значению.
Он читает буфер во время вызова и хранит собственные строки, не сохраняя view.
Закодированные байты можно передать из вектора, массива или участка большего буфера.
Исходные файлы .fotxt
Именуйте каждый исходный файл так:
<TextPack>.<Language>.fotxt
Базовое имя должно содержать ровно эти два сегмента, разделённые точкой. Engine рассматривает языковой суффикс как непрозрачную строку и не требует четырёхсимвольного кода локали. Проект может принять собственное соглашение об именовании, но оно не входит в переиспользуемый контракт парсера.
Каждая логическая запись содержит три поля в фигурных скобках:
{Key1}{Key2}{Text}
Например:
{Welcome}{}{Welcome to the wasteland.}
{QuestName}{Short}{A difficult choice}
{LongMessage}{}{First line
Second line}
Имя файла задаёт Collection; исходная запись задаёт Key1, Key2 и
значение. Key3 остаётся пустым. Collection и Key1 не могут быть пустыми.
Key2 и текстовое значение могут быть пустыми.
Только третье поле может продолжаться на следующих физических строках. Парсер добавляет символы новой строки, пока не найдёт первую закрывающую фигурную скобку. Экранирования для литеральной закрывающей скобки внутри значения нет, а данные после третьего поля не входят в запись.
Граница комментария
Отдельной грамматики комментариев нет: пропускаются только строки без открывающей
фигурной скобки. Не вставляйте скобки в заголовки и комментарии: их группы могут
создать запись или сорвать запекание. Примеры ключей оставляйте в блоках кода
документации; отклоняйте инструменты, вставляющие скобки аннотаций в комментарии .fotxt.
Варианты
Повторяющиеся полные ключи остаются отдельными вариантами:
{Ambient}{Dust}{The wind scrapes across the road.}
{Ambient}{Dust}{A sheet of dust hides the horizon.}
Game.GetText(key) и поиск с явным языком выбирают случайный вариант с равными
вероятностями. Для постоянного индекса используйте Game.GetText(key, index);
индексация начинается с нуля, количество вариантов даёт Game.GetTextCount(key).
Не предполагайте, что каждый язык содержит одинаковое количество и порядок вариантов. Нормализация языков выравнивает наличие ключей, но не количество дубликатов.
Нормализация языков
Baking.BakeLanguages является упорядоченным списком и не может быть пустым.
Каждая запись имеет форму language или child:parent. Первый элемент задаёт
базовый язык по умолчанию; явный parent обязан находиться раньше child, а имена
языков должны быть уникальны. Bare-запись после первой использует fallback на
первый язык. Например, russ engl ru18:russ en18:engl создаёт две fallback-цепи
adult overlay. Значение Engine по умолчанию — engl; подключаемые проекты обычно
переопределяют его в своём .fomain.
Для изменившегося исходного пакета TextBaker собирает все настроенные
языковые файлы этого пакета. Исходник базового языка обязателен. Суффиксы
файлов для неподдерживаемых языков сопровождаются предупреждением и
пропускаются.
Затем TextPack::FixPacks нормализует каждый небазовый язык относительно его
явного parent либо первого языка, если parent не задан:
- удаляет языки, отсутствующие в
Baking.BakeLanguages; - добавляет отсутствующие настроенные языки;
- удаляет пакеты, которых нет в выбранном fallback-языке;
- копирует пакеты, отсутствующие в child-языке;
- добавляет отсутствующие в child-пакете ключи со значениями fallback-языка;
- удаляет ключи, отсутствующие в fallback-пакете.
Fallback завершается во время запекания. После загрузки бинарного пакета поиск во время выполнения не обращается к базовому языку.
Имя бинарного файла:
<ResourcePack>.<TextPack>.<Language>.fotxt-bin
TextPack::LoadFromResources требует ровно эти три сегмента базового имени.
Все файлы выбранного языка добавляются до одной стабильной сортировки; повторы
сохраняют порядок прежних строк, файлов и записей. Пустой язык выбирает все
языки без подстановки резервного. Ёмкость хранилища растёт геометрически.
Число записей и длины ключей/значений проверяются до выделения памяти.
Ошибки декодирования вызывают DataReadingException. Явный завершающий вызов
EnsureSorted() сортирует только завершённую загрузку; при отказе нет ни сортировки, ни отката.
Чтение неотсортированного пакета отклоняется; успешная повторная загрузка сортирует
и сохранённые записи. Ошибка самой сортировки уходит вызывающему коду. Пустые пакеты и
неиспользованные байты в конце остаются допустимыми.
Поля $Text прототипов
Секции прототипов могут задавать локализованные значения без отдельных исходных текстовых файлов:
[ProtoItem]
$Name = LaserRifle
$Text engl Name = Laser rifle
$Text engl Desc Short = Compact description
$Text russ Name = Localized name
Грамматика:
$Text [Language] [Key2] [Key3] = Value
Вместе с $Text допускается не более четырёх токенов ключа. Идентификатор
прототипа становится Key1. Если Language опущен, поле использует первый
элемент Baking.BakeLanguages. Значения проходят через
StringEscaping::DecodeString, поэтому последовательности наподобие \n
становятся настоящими переводами строки.
Родительские поля $Text рекурсивно собираются до полей потомка. Потомок
наследует отсутствующие точные ключи и заменяет унаследованное значение, когда
задаёт тот же ключ $Text Language Key2 Key3.
ProtoTextBaker создаёт пять пакетов для каждого настроенного языка:
| Тип прототипа | Генерируемый пакет |
|---|---|
| Item | Items |
| Critter | Critters |
| Map | Maps |
| Location | Locations |
Другая неэкспортируемая сущность или fixed type с HasProtos |
Protos |
Все пять выходов существуют, даже если некоторые пусты. Неподдерживаемые языки
$Text сопровождаются предупреждением и пропускаются. Если несколько
исходников прототипов создают одинаковый полный ключ в одном пакете и языке,
запекание завершается ошибкой, а не зависит от порядка обхода.
Script API времени выполнения
Точными экспортируемыми сигнатурами владеет сгенерированный справочник методов. Важен следующий поведенческий контракт:
| API | Стороны | Поведение |
|---|---|---|
Game.GetLanguage() |
server, client, mapper | Возвращает текущую настройку Language как LanguageName. |
Game.GetText(key) |
client, mapper | Возвращает равномерно случайный вариант текущего языка. Отсутствующий ключ даёт пустую строку. |
Game.GetText(key, index) |
client, mapper | Возвращает вариант текущего языка по индексу с нуля. Отсутствующее значение или выход за диапазон возвращает пустую строку; отрицательный индекс вызывает исключение. |
Game.GetText(langName, key) |
client, mapper | Использует текущий пакет при пустом или текущем langName; иначе загружает и кеширует указанный язык и равномерно выбирает среди его вариантов. Для отсутствующего непустого языка runtime fallback не применяется. |
Game.GetTextCount(key) |
server, client, mapper | Возвращает количество вариантов или ноль при отсутствии. |
Game.IsTextPresent(key) |
server, client, mapper | Сообщает, существует ли хотя бы один вариант. |
Game.ChangeLanguage(langName) |
client, mapper | Заменяет текущий runtime-пакет; immutable startup setting не меняется. |
При запуске клиент загружает Client.Language, затем текущий язык становится live state движка и доступен через Game.CurrentLanguage / Game.GetLanguage(). Game.ChangeLanguage не
проверяет идентификатор и не вызывает callback игры для обновления GUI.
Подключаемый проект владеет списком разрешённых языков, политикой сохранения и
последовательностью обновления или перестроения интерфейса.
Сервер загружает один пакет из startup setting Client.Language, затем владеет тем же current-language state. Скриптам доступны только
проверка наличия и подсчёт; server-side overload Game.GetText не входит в
контракт Engine.
Граница форматирования Engine и проекта
TextPack хранит непрозрачные строки. Engine не определяет @pname@,
@nname@, @sex@, @rnd@, @arg@, @text@, разделители вариантов,
сериализацию именованных аргументов или dialog-specific раскрытие lexem. Такие
возможности, если они присутствуют, принадлежат скриптам и документации
подключаемого проекта.
Клиентский рендерер шрифтов владеет встроенными цветовыми тегами. Точный порядок байтов и взаимодействие флагов также закреплены в сгенерированном справочнике рендеринга шрифтов:
@color:BBGGRR@
@color:AABBGGRR@
@color:0xBBGGRR@
@color:0xAABBGGRR@
@color@
Шесть шестнадцатеричных цифр задают цвет без явного байта alpha; восемь
включают alpha. Пустой тег @color@ восстанавливает предыдущий цвет. Теги
удаляются при форматировании шрифта. При FontFlag.NoColorize допустимые теги
по-прежнему удаляются, но весь текст использует базовый цвет.
Если проект добавляет ещё один проход форматирования, документируйте и проверяйте:
- какая сторона его выполняет;
- запускается ли он до или после поиска текста;
- разрешены ли вложенные запросы;
- экранирование и поведение при некорректных входных данных;
- кто отвечает за случайный выбор;
- взаимодействие с цветовыми тегами рендерера.
Процесс авторинга
- Выберите владеющий пакет и семантический полный ключ.
- Создайте базовый язык, затем настроенные переводы с его ключами.
- Используйте повторы для намеренных вариантов; не вставляйте скобки в комментарии
.fotxt. - Используйте
$Textдля принадлежащих прототипу имён и описаний. - Запеките ресурсы; исправляйте ошибки разбора, отсутствующей базы и пересечений в исходниках.
- Визуально проверьте смену языков и каждую обработку шаблонов проекта.
Процесс проверки
Сопровождающие Engine при изменении TextPack, TextBaker, ProtoTextBaker,
языковых настроек, script-методов текста или разбора встроенных цветов должны
в той же правке обновить BuildTools/TextFormatInterface.json, это руководство
и сгенерированные результаты:
python BuildTools\docs_text_format.py --write
python BuildTools\docs_text_format.py --check
python -m unittest BuildTools.tests.test_docs_text_format
При изменении поведения запускайте сфокусированные native-тесты TextPack,
TextBaker и ProtoTextBaker. Изменения цветового парсера проверяйте ближайшими
тестами рендеринга и видимым клиентом. Затем повторно запеките подключаемый
проект. Проектные каталоги пакетов, проверки переводов, formatter-ы lexem и
обновление GUI требуют тестов проекта, а не fixtures Engine.
Маршрутизация сопровождения
- исходный синтаксис, идентичность ключей, варианты, бинарная загрузка или
нормализация:
Source/Common/TextPack.*; - выбор имени файла, инкрементальное заполнение пакета или исходный output:
Source/Tools/TextBaker.cpp; $Textпрототипа, наследование, маршрутизация пакетов или пересечения:Source/Tools/ProtoTextBaker.cpp;- script lookup и переключение:
Source/Scripting/*GlobalScriptMethods.cppвместе сSource/Client/Client.cppилиSource/Server/Server.cpp; - встроенные цветовые теги и всё поведение layout/rendering шрифтов:
Source/Client/FontManager.*и форматах шрифтов и компоновке текста; - имена пакетов, порядок языков, переводы, lexem или обновление GUI проекта: подключаемый проект.