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

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

Это руководство описывает переиспользуемый контракт FOnline Engine для исходных файлов .fotxt, локализованных полей $Text прототипов, запекания языков, поиска текста во время выполнения и встроенных цветовых тегов, которыми владеет рендерер. Точный набор правил для текущей ревизии приведён в справочнике формата текста и канонической JSON-модели. Дескрипторы растровых шрифтов, привязка слотов, покрытие глифов, измерение, перенос строк и полный контракт флагов рендерера принадлежат форматам шрифтов и компоновке текста.

Подключаемая игра владеет приоритетами языков, каталогом пакетов, семантическими именами ключей, процессом перевода и любым formatter поверх полученных строк. Документация Engine не должна зависеть от пакетов или lexem конкретного проекта.

Модель текстового пакета

Каждое текстовое значение хранится под ключом TextPackKey:

Collection + Key1 + Key2 + Key3

Collection имеет тип TextPackName, а остальные поля являются значениями hstring. Язык намеренно не входит в ключ. Поэтому один ключ может существовать в запечённом пакете каждого языка.

В основе используется vector, отсортированный по полному ключу. Несколько соседних значений одного ключа являются вариантами. Каждый путь загрузки, слияния и исправления fallback восстанавливает порядок до возврата; методы чтения проверяют invariant и используют один binary-search range, не изменяя и не исправляя разделяемое состояние. Это сохраняет безопасность concurrent reads. Game.GetTextCount(key) возвращает количество вариантов, а Game.GetText(key, index) выполняет выбор по индексу с нуля.

Исходные файлы .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 и текстовое значение могут быть пустыми.

Только третье поле может продолжаться на следующих физических строках. Парсер добавляет символы новой строки, пока не найдёт первую закрывающую фигурную скобку. Экранирования для литеральной закрывающей скобки внутри значения нет, а данные после третьего поля не входят в запись.

Граница комментария

В разборе исходного текста нет грамматики токенов комментариев. Физическая строка пропускается только тогда, когда парсер не может найти первую открывающую фигурную скобку. Проект может использовать заголовки без скобок или строки с # для читаемости, но предполагаемый комментарий с группами в фигурных скобках способен стать записью или привести к ошибке запекания.

Считайте это правилом проверки контента:

  • не используйте { и } в комментариях и заголовках;
  • показывайте формы ключей в fenced-блоках документации, а не в комментариях .fotxt;
  • отклоняйте инструменты редактирования, добавляющие фигурные скобки аннотаций в исходники текстовых пакетов.

Варианты

Повторяющиеся полные ключи остаются отдельными вариантами:

{Ambient}{Dust}{The wind scrapes across the road.}
{Ambient}{Dust}{A sheet of dust hides the horizon.}

По умолчанию script API не выбирает случайный вариант. Game.GetText(key) выбирает первый вариант. Для намеренного случайного выбора получите количество через Game.GetTextCount(key), выберите индекс в коде проекта и передайте его в Game.GetText(key, index).

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

Нормализация языков

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 требует ровно три таких сегмента базового имени.

Поля $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. Добавляйте настроенные переводы, не изобретая отсутствующие в базе ключи.
  4. Используйте повторяющиеся ключи только тогда, когда вызывающая сторона намеренно обрабатывает варианты.
  5. Не используйте фигурные скобки в комментариях исходных .fotxt.
  6. Используйте $Text для принадлежащих прототипу имён и описаний.
  7. Запекайте ресурсы и считайте каждую ошибку парсера, отсутствующей базы или пересечения ошибкой исходного контента.
  8. Проверяйте переключение языка и каждый formatter проекта в видимом клиенте.

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

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