FOnline Engine
Current 2026.1.58-dev GitHub
Документация Docs/ru/how-to/content/text-and-localization.md

Текст и локализация

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 не задан:

  1. удаляет языки, отсутствующие в Baking.BakeLanguages;
  2. добавляет отсутствующие настроенные языки;
  3. удаляет пакеты, которых нет в выбранном fallback-языке;
  4. копирует пакеты, отсутствующие в child-языке;
  5. добавляет отсутствующие в child-пакете ключи со значениями fallback-языка;
  6. удаляет ключи, отсутствующие в 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 допустимые теги по-прежнему удаляются, но весь текст использует базовый цвет.

Если проект добавляет ещё один проход форматирования, документируйте и проверяйте:

  • какая сторона его выполняет;
  • запускается ли он до или после поиска текста;
  • разрешены ли вложенные запросы;
  • экранирование и поведение при некорректных входных данных;
  • кто отвечает за случайный выбор;
  • взаимодействие с цветовыми тегами рендерера.

Процесс авторинга

  1. Выберите владеющий пакет и семантический полный ключ.
  2. Создайте базовый язык, затем настроенные переводы с его ключами.
  3. Используйте повторы для намеренных вариантов; не вставляйте скобки в комментарии .fotxt.
  4. Используйте $Text для принадлежащих прототипу имён и описаний.
  5. Запеките ресурсы; исправляйте ошибки разбора, отсутствующей базы и пересечений в исходниках.
  6. Визуально проверьте смену языков и каждую обработку шаблонов проекта.

Процесс проверки

Сопровождающие 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 проекта: подключаемый проект.
Введите запрос.