Локализация игр и приложений в Unity. Быстро и удобно

  • Tutorial
Привет! В этой статье я поделюсь своим опытом локализации игр и приложений в Unity, а также расскажу о своем плагине Simple Localization, который доступен в Asse Store. Уровень статьи — Easy. Кода не будет вообще, он вам не пригодится.



С точки зрения общей концепции все просто. У нас должен быть список словарей для каждого языка с одинаковыми ключами, из которых мы и будем получать локализованные значения. С технической стороны нужно определить:

  • Какими будут наши словари?
  • Где они будут храниться?
  • Как мы будем их редактировать и расширять?
  • Как реализовать связку с интерфейсом приложения?

При реализации я руководствовался двумя принципами: простота и удобство. Итак, самый простой структурированный формат, который подойдет для словарей — CSV (Comma Separated Values). Это текстовый файл, в котором все ячейки разделены разделителем — либо запятой (","), либо точкой с запятой (";"), в зависимости от региональных настроек. CSV можно открыть и редактировать в любом текстовом редакторе, но лучше всего для этого подойдет Excel. Храниться CSV будут, конечно же, в папке Resources, чтобы приложение в любой момент могло прочитать их.

Формат будет следующий: первая колонка — ключи, все последующие колонки — словари. Первая строка — заголовок. Можно иметь один общий CSV, если текстов в приложении немного, а можно разбить на несколько CSV по какому-либо принципу, например: меню, настройки, достижения и т.д.

Идем дальше. Редактирование локализации в Excel, это, конечно, здорово, но я предлагаю загрузить их в Google Sheets. Тогда редактировать их можно будет из любого места, в любое время и с любого устройства. Но самое главное — локализацию можно будет расшарить переводчикам, и у них не будет никаких сложностей с переводом. Посмотреть на словарь можно по ссылке.


И последний пункт — интеграция с Unity, с uGUI. Тут тоже все просто — на каждый текстовый компонент Text добавим свой компонент LocalizedText, в котором укажем ключ. При запуске этот компонент получит локализованное значение и установит его в Text.



Дальше возникает несколько вопросов:

  • Как и когда инициализировать словарь? Достаточно сделать вызов LocalizationManager.Read из управляющего скрипта. Это можно сделать в Awake или Start, если вас устроит автоматическое определение языка (Application.systemLanguage), или после загрузки профиля, когда уже будут известны пользовательские настройки.
  • Что делать, если мы заранее не знаем, какое именно значение будет иметь компонент Text? Не добавляем компонент LocalizedText, а потом присвоим ему значение из кода, обратившись к словарю через LocalizationManager.Localize(string localizationKey).
  • Что делать, если в локализованное значение нужно вставить параметр? Просто используем в локализованной строке форматирование, например «Level {0}», а потом через string.Format выполним подстановку. Для этого есть перегрузка LocalizationManager.Localize(string localizationKey, params object[] args).
  • Как изменить локализацию в runtime? Просто меняем язык через LocalizationManager.Language. При этом возникнет событие, на которое подписаны все компоненты LocalizedText. Однако, локализованные из кода компоненты Text (у которых нет компонента LocalizedText) нужно будет обновить вручную.
  • Что делать, с другими элементами, например, с выпадающим списком Dropdown? Очевидно, использовать компонент LocalizedDropdown =)

Ну и последняя плюшка в моем ассете — он умеет автоматически скачивать все листы из Google Sheets и сохранять их в ресурсах игры. Для этого есть компонент LocalizationSync, у которого есть кнопка Sync (в инспекторе).

Скачать можно в Asset Store: Simple Localization.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 35
    +1

    2018 год на дворе, а люди по-прежнему занимаются копи-пастингом переводов из/в CSV и Excel.


    Существуют же gettext а также миллиард программ и сервисов для перевода файлов gettext. Строки автоматически собираются из исходного кода (никто не должен вручную проверять и вносить правки при изменении кода/ресурсов), переводчики сразу видят строки, которые надо перевести, переводы валидируются (например, чтобы не возникало ошибок из-за опечаток в названиях placeholder'ов, таких как Сишные %s, %d), после перевода все переведенные строки автоматически доступны приложению.


    Для перевод po файлов существует просто прорва онлайн сервисов, GUI программ, self-hosted сервисов (я, например, использую сейчас Weblate).


    Как при всем таком многообразии инструментов, которые значительно автоматизируют и облегчают процесс, рекомендовать до сих пор CSV и Excel?.. Неужели Unity не поддерживает ничего лучше?

      0
      Увы, мне об этом не известно. Вот официальный гайд на эту тему: unity3d.com/ru/learn/tutorials/topics/scripting/localization-manager
        0

        Наверное в играх строк немного. Просто я работал в проектах, где локализация производится через Excel. Это просто средневековье какое-то. Людям приходится вручную заниматься монотонным копированием строк туда-сюда, в котором очень легко ошибиться. Неблагодарная тяжелая работа, которая элементарно автоматизируется, и решения для автоматизации придуманы очень давно (GNU gettext появился в 1995 году).

        0
        Стоит также учесть специфику разработки. Если вы начинающий инди-разработчик, который пришел к Unity именно из-за низкого порога входа и C#, то вряд ли вам нужно все то, о чем вы рассказываете. Мне, например, определенно хватает и CSV.
          0

          Так в gettext ничего сложного нет. Строки помечаются в исходном тесте (чаще всего используется синтаксис _("строка")). Утилиты gettext сканируют исходный текст, находят отмеченные строки и записывают в .po файл.


          .po файл можно переводить вручную, как тот же csv, можно преобразовать в csv, можно редактировать GUI редактором (существует множество), можно отдать на аутсорс, можно загрузить в онлайн-сервис наподобие OneSky, можно установить свой веб-сервис, такой как Weblate (у Weblate есть еще не менее 5 аналогов).


          Затем полученный переведенный файл распространяется вместе с продуктом, и во всех местах вызов gettext (_("строка")) подставляет переведенный текст. Все, кроме собственно перевода, полностью автоматизировано. Перевод, в зависимости от выбранного решения, тоже можно полностью или частично автоматизировать.


          Один раз настраивается, затем все происходит автоматически. Один раз попробовав, уже не захочется ничего другого.


          Ну а CSV я тоже работал, конечно. Когда строк больше сотни, тут уже на каждой итерации требуется значительное количество ручного труда.

            0
            Обычно фиксированных строк в геймдеве мало и они пробиваются не в коде, а в метаданных сцены юнити (можно принудительно заставить сериализовать эти данные в YAML, а потом долго и мучительно выковыривать их, но...), большая же часть собирается динамически и практически никогда не забивается строками на «original»-языке в коде, а сразу едет как токен в локализацию. Т.е сканировать как бы и нечего.
              0
              immaculate про хорошую вещь говорит, тут вопрос целесообразности и применимости.
                0
                Тут фишка в том, что CSV можно использовать не только как данные локализации, но и как таблицы для баланса игровой механики — неплохая замена ScriptableObject и не завязано на юнити и ее апдейты, которые всегда-что-то ломают.
                  0
                  Ох, внешние параметры для игры — это отдельная тема. Но Unity работают над этим, может что-то и выйдет в ближайшее время.
                    0
                    Ну вот простой пример.
                    Задаются ограничители данных, списки выбора и т.п. Первая строка — имена JSON-полей, вторая строка — символы для экранирования (по сути определяют тип данных). От дизайнера защищены как 2 первые строки, так и первый столбец. Это все дампится через CSV, потом автоматически конвертится в JSON и складывается в Resources. В рантайме уже происходит загрузка и маппинг прямо на внутренние классы через JsonUtility или что-то иное.
                      0
                      Я раньше делал еще проще — хранил сериализованный конфиг игры. А игра еще и умела его подгружать в процессе, чтобы билды новые не делать из-за изменения баланса. Но это уже совсем другая история)
                        0
                        Ну тут смысл такой же — дизайнеру даже не нужно иметь установленную юнити, можно прикрутить подгрузку / парсинг в рантайме. Ну и совместное редактирование с историей правок и возможностью применения формул для балансировки.
                        0
                        А мы подняли сервер на гуглокоде, который через api docs по схожему принципу вытаскивает данные и просто отдает JSON в Unity, где его из промежуточного формата (если требуется) превращают в ScriptableObject.
                          0
                          Тоже вариант, хотя и более тяжелый по реализации и поддержке. Основной посыл был — избавиться от необходимости крутить циферки в юнити и рисования кастомных инспекторов. Ну и если потребуется — это все можно провернуть на лету, немного подхачив код, тогда вообще можно будет подсасывать данные прямо в билде. Со SO такое невозможно.
                            0
                            Реализации да, поддержка — нет. Один раз настроил и работает. У каждой таблицы добавляется еще одна вкладка Schema — которая описывает формат JSON который нужно сформировать для Unity.
                            Но там без связей. И вот связи приходится на клиенте восстанавливать. Инспектор кастомный делать не нужно, кнопку скачивания можно вынести в меню.
                            То что надо на лету подсасывать, это уже должно быть из нормальной базы данных с нормальным бекэндом. Api гугла медленное, чтобы не злоупотребляли. Для ГД еще можно сделать, для игроков — нет. Поэтому скрипты для работы я в Editor засунул.
                              0
                              Я про поддержку апи гугля — оно может меняться и за этим нужно следить. А стаскивание данных по прямому урлу работает с момента существования гуглодоков. Работает довольно быстро и без временного лага до 30 минут, как в в штатной публикации в веб. Понятно, что это не для продакшна, но для быстрого тестирования у дизайнеров / внутренних тестировщиков с изменением циферок на лету — вполне сойдет.
                                0
                                За год не сталкивался с такими изменениями. Поживем увидим :). Главное чтобы данные не пропали, а как их достать — всегда можно придумать.
            0
            Вот наткнулся на похожее в Unity: www.unitytutorials.ca/systems/unity-localization-using-po
            И почему я раньше не услышал о таком
              0

              В статье, насколько я понял, переведенные строки загружаются из .po файла. Обычно готовый po файл компилируется в бинарный .mo файл: https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html


              Не знаю, насколько критична разница в скорости доступа.

            0
            Как обрабатываются многострочные ячейки? Имеется ввиду строка, в которой прямо внутри набиты переводы строк (не через пару символов "\n"). Такие csv тоже являются, как ни странно, валидными, пусть и строка таблицы разбита в файле на несколько подстрок.
              0
              У меня не было проблем с ними. Ведь служебные символы так и переезжают в локализацию, где их рендерит компонент Text.
                0
                Это проблема парсинга таких CSV, это специальный кейс, который нужно обрабатывать — это делается?
                  0
                  Да, только кавычки остаются. Но код открытый, простой и будет только рад допиливанию)
                0
                Кстати, переносы строк экранируются двойными кавычками при скачивании CSV. Так что проблем не будет. Главное, чтобы парсер корректно это прочитал. Мой парсер такое обрабатывает, но оставляет кавычки. Исправлю как нибудь)
              0
              А как решается проблема, когда нужно изменение самого UI? Например, на разных языках размер элемента должен быть разным.
                0
                Ну есть же всякие ContentSizeFitter и HorizontalLayoutGroup. Хотя я обычно просто устанавливаю динамичный размер текста, чтобы он вписывался в размер элемента.
                  0
                  Если вы используете Best Fit для текстовых компонентов, то он плохо сказывается на производительности, особенно если у вас есть какие-то прокручиваемые списки с кучей текстов
                    –2
                    Я человек простой. Если пользователи жалуются на тормоза — я буду искать причину. В остальное время я нацелен на конечный результат. В процессе работы меня мало интересуют нюансы реализации локализации и специфика работы uGUI. Главное, чтобы заработало побыстрее. Все эти паттерны, рефакторинги и оптимизация — кому они нужны, если проект не зашел? Да, есть и такие, кто считает, что в паттернах и всяких крутых вещах, вроде gettext, и есть суть разработки. Их право.
                      0
                      Те которые жалуются, это лишь малая, большинство недовольных просто закрывают апп и удаляют. И вы даже не узнаете причину.
                        0
                        Есть и другая точка зрения. Если приложение или игра того стоит — то буду жаловаться. А если нет, то оптимизация не поможет.

                        Кроме того, аналитику и Performance Reporting никто не отменял.
                          0
                          Да, такое тоже имеет место быть.
                0
                CSV (Comma Separated Values). Это текстовый файл, в котором все ячейки разделены разделителем — либо запятой (","), либо точкой с запятой (";")


                Наличие точки или точки с запятой в самом тексте в одной из ячеек не порушит структуру файла?
                  0
                  Excel и Google Sheet экранируют такие ячейки в кавычки.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое