Современная гибкая система локализации для Unity. Управляет переводами, локализованными ассетами (спрайты, звуки, любые UnityEngine.Object), шрифтами для разных языков, плюрализацией, переключением языка во время игры и удобным редактором — всё без внешних зависимостей.
На других языках: English.
- Возможности
- Установка
- Быстрый старт
- Структура папок
- Окно редактора
- Runtime API
- Компоненты
- Плюрализация и параметры
- Профили языков и шрифты
- Цепочки fallback'ов
- Локализованные ассеты
- Dependency Injection
- Расширяемость
- Атрибуты
- Миграция с v1
- Локализация текста — JSON, по одной папке на язык, несколько файлов на язык объединяются автоматически
- Локализация ассетов — спрайты, аудио, материалы, префабы или любой
UnityEngine.Objectчерез таблицы ассетов; единый runtime-APILocalization.GetAsset<T>(key)для всех типов - Плюрализация — встроенные правила для германских, славянских, романских, восточноазиатских и арабского языков, через простой inline-синтаксис (
{0|яблоко|яблока|яблок}) - Подстановка параметров — индексированные (
{0}), именованные ({playerName}) или смешанные - Профили языков — на каждый язык: TMP-шрифт + fallback-шрифт, legacy-шрифт, множитель размера, толщина, межсимвольный/межстрочный/межсловный интервал, направление текста (LTR/RTL), переопределение выравнивания
- Per-language fallback chain — например,
украинский → русский → английский; каждый профиль указывает на свой fallback, обход с защитой от циклов - Per-component override профилей — отдельные UI-элементы могут переопределять конкретные секции (только шрифт, только spacing и т.д.) из глобального профиля
- Переключение языка во время игры — один вызов обновляет все активные компоненты через события
- Богатый редактор — виртуализированная таблица переводов, поиск, drag-and-drop таблиц ассетов, inline-превью, undo/redo, переименование ключей с автообновлением ссылок в сценах и префабах, анализ покрытия, экспорт в CSV
- Расширяемость — кастомные рендереры превью и фильтры типов через простые интерфейсы +
TypeCache-обнаружение; кастомные табы через атрибут[LocalizationEditorTab] - DI-friendly — статический фасад
Localizationдля простоты или прямая инъекцияLocalizationManagerдля тестируемости - WebGL — встроенный мост для логирования
- Без сторонних пакетов — чистый C#
Вариант A — OpenUPM CLI (рекомендуется):
npm install -g openupm-cli
openupm add com.renkoff.simply-localizeВариант B — вручную через Packages/manifest.json:
- Добавь scoped registry:
{
"scopedRegistries": [
{
"name": "OpenUPM",
"url": "https://package.openupm.com",
"scopes": [
"com.renkoff"
]
}
],
"dependencies": {
"com.renkoff.simply-localize": "2.0.0-alpha.2"
}
}- Открой
Window → Package Manager - Нажми
+ → Add package from git URL - Вставь:
https://github.com/RenKOFFF/Simply-Localize-Localization-System-for-Unity.git?path=src/SimplyLocalize
Добавь в Packages/manifest.json:
{
"dependencies": {
"com.renkoff.simply-localize": "https://github.com/RenKOFFF/Simply-Localize-Localization-System-for-Unity.git?path=src/SimplyLocalize"
}
}Create → SimplyLocalize → Localization Config
Помести его в любую папку Resources чтобы он автоматически загружался при старте.
Create → SimplyLocalize → Language Profile для каждого языка (English, Russian, Japanese и т.д.)
Заполни поля languageCode, displayName, systemLanguage и добавь профиль в список LocalizationConfig.languages.
Совет: Языки можно создавать прямо из окна редактора через таб Languages — оно само сгенерирует структуру папок и JSON-файлы.
Window → SimplyLocalize → Localization Editor
В окне 8 табов: Translations, Assets, Languages, Profiles, Coverage, Auto Localize, Tools, Settings.
В табе Translations:
- Нажми
+ Add keyв тулбаре - Введи ключ (например,
UI/MainMenu/Play) и выбери файл - Заполни переводы для всех языков inline в таблице
using SimplyLocalize;
using UnityEngine;
public class GameBootstrap : MonoBehaviour
{
[SerializeField] private LocalizationConfig _config;
private void Awake()
{
// Инициализация с явным конфигом
Localization.Initialize(_config);
// Или авто-определение системного языка
Localization.SetLanguageAuto();
}
}Альтернатива — включи Auto Initialize в ассете LocalizationConfig, и система загрузится сама из Resources ещё до загрузки сцены.
string greeting = Localization.Get("UI/Welcome");
string score = Localization.Get("UI/Score", 100);
Sprite flag = Localization.GetAsset<Sprite>("flags/current");Или просто кинь компонент LocalizedText на любой TextMeshPro / legacy Text и выбери ключ в Inspector — без кода.
Локализационные данные лежат в любой папке Resources:
Assets/
└── Resources/
└── Localization/ ← базовый путь (настраивается в LocalizationConfig)
├── _meta.json ← метаданные ключей (описания, история переименований)
├── en/
│ ├── text/
│ │ ├── global.json
│ │ ├── ui.json
│ │ └── items.json
│ └── AssetTable.asset ← локализованные спрайты/звуки/etc для английского
├── ru/
│ ├── text/
│ │ ├── global.json
│ │ ├── ui.json
│ │ └── items.json
│ └── AssetTable.asset
└── ja/
├── text/
│ └── ...
└── AssetTable.asset
Несколько JSON-файлов на один язык объединяются при загрузке — разделяй ключи по фичам (ui.json, items.json, dialogue.json и т.д.) для лучшей организации и удобства мерджа в системе контроля версий.
{
"translations": {
"UI/MainMenu/Play": "Играть",
"UI/MainMenu/Quit": "Выход",
"Game/Score": "Очки: {0}",
"Game/Coins": "У вас {0} {0|монета|монеты|монет}",
"Dialogue/Greeting": "Привет, {playerName}!"
}
}- Виртуализированный список — без лагов даже на тысячах ключей
- Вложенные группы — ключи типа
UI/Popup/Titleавтоматически складываются в деревоUI → Popup → Title - Поиск — фильтр по ключу или значению с дебаунсом 150мс
- Множественный выбор — Ctrl+клик / Shift+клик
- Inline-редактирование — все языки колонками, клик по любой ячейке для редактирования
- TAB / Shift+TAB — навигация между ячейками без мыши
- Undo/Redo — Ctrl+Z / Ctrl+Y
- Отсутствующие переводы подсвечены красным
- Контекстное меню на любом ключе: копировать, переименовать (с автообновлением ссылок), переместить в другой файл, удалить, добавить описание
- Описания ключей хранятся в
_meta.json— отображаются под ключом в таблице
- Управление локализованными ассетами (спрайты, аудио, материалы, меши, что угодно)
- Динамические фильтры по типам — список фильтров в dropdown генерируется автоматически из типов, которые реально лежат в твоих таблицах
- Древовидное представление — сначала по типу ассета, затем по пути ключа
- Inline-превью — клик по ▶ рядом с ключом разворачивает полные превью на каждый язык, со специализированным рендерингом для спрайтов (с учётом UV-rect атласов), текстур, аудио (с кнопкой Play)
- Drag-and-drop — кидай любой ассет прямо в ячейку нужного языка
- Поиск и переименование — тот же workflow что и в табе Translations
- Авто-типизация полей — в режиме "All" каждый ObjectField для ключа сужен до того типа, что уже назначен (нельзя случайно подменить Sprite на AudioClip)
- Список всех настроенных языков с badge'ами типов контента (показывает какие типы ассетов есть в каждом языке)
- Селекторы Default и Fallback языков
- Отображение fallback-цепочки (
Fallback: ru → en (global)) - Создание нового языка с автогенерацией папок и JSON
- Добавление существующих LanguageProfile-ассетов в конфиг
- Удаление языка (два варианта: только из конфига, или вместе со всеми данными)
Встроенный inspector для любого LanguageProfile ассета — редактируй шрифты, типографику, spacing, layout/направление прямо здесь, не ища ассет в Project window.
- Прогресс-бары покрытия по каждому языку (переведено / всего)
- Предупреждения о пропущенных переводах
- Предупреждения о несовпадении параметров (например, в reference есть
{playerName}, а в переводе нет)
Сканирует сцену на все TMP_Text / Text компоненты и массово добавляет к ним LocalizedText с автогенерируемыми ключами.
- Экспорт в CSV (для отправки в переводческие сервисы)
- Импорт из CSV
- Сортировка ключей
- Поиск неиспользуемых ключей
- Режим конвертации ключей (как обрабатываются пробелы при создании новых ключей)
- Тогглы логирования
using SimplyLocalize;
// Инициализация
Localization.Initialize(config); // явный конфиг
Localization.Initialize(config, "ru"); // явный язык
Localization.Initialize(); // авто-поиск в Resources
Localization.SetLanguageAuto(); // подобрать под язык устройства
// Текст
string s1 = Localization.Get("UI/Welcome");
string s2 = Localization.Get("UI/Score", 100);
string s3 = Localization.Get("UI/Stats",
new object[] { 10, 20 },
new Dictionary<string, object> { { "playerName", "Alex" } });
// Ассеты (generic, работает для ЛЮБОГО UnityEngine.Object)
Sprite flag = Localization.GetAsset<Sprite>("flags/current");
AudioClip voice = Localization.GetAsset<AudioClip>("voice/intro");
Material mat = Localization.GetAsset<Material>("fx/hit");
AnimationClip anim = Localization.GetAsset<AnimationClip>("anim/idle");
// Переключение языка
Localization.SetLanguage("ru");
Localization.SetLanguage(russianProfile);
// Запросы
bool exists = Localization.HasKey("UI/Welcome");
bool hasRu = Localization.HasTranslation("UI/Welcome", "ru");
string current = Localization.CurrentLanguage;
LanguageProfile profile = Localization.CurrentProfile;
// События
Localization.OnLanguageChanged += OnLangChanged;
Localization.OnProfileChanged += OnProfileChanged;
// Shutdown и перезагрузка
Localization.Shutdown();
Localization.Reload();См. Dependency Injection.
Локализует TextMeshPro или legacy Text компонент.
- Добавь
LocalizedTextна GameObject где есть text-компонент - Выбери ключ из dropdown'а (поисковый popup со всеми доступными ключами)
- Готово — текст автоматически обновляется при смене языка
// Опционально: смена ключа из кода
GetComponent<LocalizedText>().Key = "UI/NewKey";То же что LocalizedText, но поддерживает параметры в runtime (индексированные и/или именованные).
var text = GetComponent<FormattableLocalizedText>();
// Индексированные: в JSON "Очки: {0}"
text.SetArgs(100);
// или установить один индекс
text.SetArg(0, 100);
// Именованные: в JSON "Привет, {playerName}!"
text.SetParam("playerName", "Alex");
// Несколько именованных за раз
text.SetParams(new Dictionary<string, object>
{
{ "playerName", "Alex" },
{ "count", 5 }
});
// Очистить все параметры
text.ClearParams();Дефолтные значения параметров можно задать в Inspector через список Parameters — удобно для статичных надписей с подстановками, для которых вообще не нужен код. Используй цифровое имя (0, 1, ...) для индексированных параметров или любую строку для именованных.
Локализует Sprite на Image (UI) или SpriteRenderer (world-space). Сам определяет какой target лежит на том же GameObject.
Локализует AudioClip на AudioSource. Опционально — авто-проигрывание при смене языка (тоггл в инспекторе).
Вызывает разные UnityEvent в зависимости от языка. Полезно для аналитики, локализованных катсцен или любых language-specific сайд-эффектов.
Переопределяет конкретные секции глобального LanguageProfile для отдельного компонента. Полезно когда, например, нужен один конкретный заголовок с увеличенным шрифтом для китайского, но без затрагивания остальных языков.
Секции, которые можно переопределить независимо: Font, Typography, Spacing, Layout. У каждой свой per-language список плюс тоггл включения/выключения.
Если нужно локализовать свой тип ассета — наследуйся от LocalizedAsset<T>:
using SimplyLocalize;
using SimplyLocalize.Components;
using UnityEngine;
[DisallowMultipleComponent]
public class LocalizedMaterial : LocalizedAsset<Material>
{
[SerializeField] private Renderer _target;
protected override void ApplyAsset(Material asset)
{
if (_target != null) _target.material = asset;
}
protected override Material ReadCurrentAsset()
{
return _target != null ? _target.material : null;
}
}И всё — базовый класс сам подписывается на событие смены языка, делает первоначальную загрузку и переприменяет ассет при переключениях. Селектор ключа в инспекторе автоматически отфильтрует только ключи с типом Material в твоих таблицах.
"Score": "Ваш счёт: {0}"Localization.Get("Score", 100); // "Ваш счёт: 100""Greeting": "Привет, {playerName}!"Localization.Get("Greeting",
new Dictionary<string, object> { { "playerName", "Alex" } });
// "Привет, Alex!"Синтаксис {N|форма1|форма2|...} выбирает форму на основе значения параметра N, используя правило плюрализации текущего языка.
"Items": "{0} {0|item|items}",
"ItemsRu": "{0} {0|предмет|предмета|предметов}"Localization.Get("Items", 1); // "1 item"
Localization.Get("Items", 5); // "5 items"
// Русские правила (one / few / many):
Localization.Get("ItemsRu", 1); // "1 предмет"
Localization.Get("ItemsRu", 3); // "3 предмета"
Localization.Get("ItemsRu", 5); // "5 предметов"| Семейство | Языки | Форм |
|---|---|---|
| Германские | Английский, немецкий, голландский, шведский, датский | 2 |
| Романские | Французский, испанский, итальянский, португальский | 2 |
| Славянские | Русский, украинский, польский, чешский, сербский | 3 |
| Восточноазиатские | Японский, китайский, корейский, тайский, вьетнамский | 1 |
| Арабский | Арабский | 6 |
Правило выбирается автоматически из LanguageProfile.systemLanguage. Можно переопределить через свою реализацию IPluralRule и регистрацию через PluralRuleProvider.
LanguageProfile хранит всё про один язык:
- Identity —
languageCode,displayName,systemLanguage - Font — основной TMP-шрифт, TMP fallback-шрифт, legacy UI Text шрифт
- Typography — множитель размера, толщина, стиль
- Spacing — корректировки межстрочного / межсимвольного / межсловного интервала
- Layout — направление текста (LTR/RTL), переопределение выравнивания
- Fallback — ссылка на другой
LanguageProfileкак per-language fallback
У каждой секции есть тоггл override. Секции с выключенным тогглом не трогают исходные значения компонента — так что можно сделать китайский профиль, который только меняет шрифт, не трогая spacing или размер.
ProfileApplier автоматически кеширует исходные значения компонента (font, size, material, spacing, alignment, RTL flag) при первом обращении и восстанавливает их когда переключаешься на профиль, который не переопределяет соответствующую секцию.
Каждый язык может указать на другой язык как на свой per-language fallback через поле fallbackProfile. Система проходит по этой цепочке прежде чем упасть на глобальный LocalizationConfig.fallbackLanguage.
Пример цепочки: украинский → русский → английский (глобальный)
LanguageProfile_uk.fallbackProfile = LanguageProfile_ru
LanguageProfile_ru.fallbackProfile = null
LocalizationConfig.fallbackLanguage = LanguageProfile_en
Когда ищем ключ в украинском:
- Смотрим данные
uk→ не найдено - Идём в
ru(per-language fallback) → не найдено - Идём в
en(глобальный fallback) → найдено! Используем. - Если всё ещё нет — возвращаем сам ключ как есть
Защита от циклов через HashSet<string> посещённых кодов.
У каждого языка есть один ScriptableObject LocalizationAssetTable в Resources/Localization/{lang}/AssetTable.asset. Таблица — это словарь key → Object, тип ассета хранится per-entry а не per-table — то есть одна таблица может содержать спрайты, аудио и кастомные типы вперемешку.
В runtime LocalizationManager загружает только таблицы текущего языка (плюс таблицы из fallback-цепочки когда нужно) — экономя память.
// Работает для ЛЮБОГО наследника UnityEngine.Object
Sprite sprite = Localization.GetAsset<Sprite>("ui/flag");
AudioClip clip = Localization.GetAsset<AudioClip>("voice/intro");
Mesh mesh = Localization.GetAsset<Mesh>("models/character");
Material material = Localization.GetAsset<Material>("fx/explosion");
MyCustomSO config = Localization.GetAsset<MyCustomSO>("configs/region");Используй таб Assets в окне локализации. Перетаскивай ассеты в ячейки, фильтруй по типам, смотри inline-превью.
Если ты предпочитаешь DI вместо статического фасада Localization, можно создавать и инжектить LocalizationManager напрямую. Все члены публичные.
using VContainer;
using VContainer.Unity;
using SimplyLocalize;
public class GameLifetimeScope : LifetimeScope
{
[SerializeField] private LocalizationConfig _config;
protected override void Configure(IContainerBuilder builder)
{
// Регистрируем как singleton
builder.Register<LocalizationManager>(Lifetime.Singleton)
.WithParameter(_config)
.AsSelf();
// Потребители инжектят LocalizationManager напрямую
builder.Register<MainMenuController>(Lifetime.Singleton);
}
}
public class MainMenuController
{
private readonly LocalizationManager _loc;
public MainMenuController(LocalizationManager loc)
{
_loc = loc;
_loc.SetLanguage("en");
}
public string GetTitle() => _loc.Get("UI/MainMenu/Title");
}public class GameInstaller : MonoInstaller
{
[SerializeField] private LocalizationConfig _config;
public override void InstallBindings()
{
Container.Bind<LocalizationManager>()
.AsSingle()
.WithArguments(_config);
}
}Встроенные компоненты (LocalizedText, LocalizedSprite и т.д.) сейчас используют статический фасад Localization внутри. Если ты хочешь полностью работать через DI, есть два варианта:
- Параллельно инициализировать статический фасад — вызови
Localization.Initialize(config)один раз при старте. Встроенные компоненты будут использовать его, а твой DI-инжектированныйLocalizationManagerможет сосуществовать рядом. Оба разделяют один и тот же конфиг. - Писать свои компоненты — наследуйся от
LocalizedAsset<T>или пиши свойMonoBehaviourкоторый получаетLocalizationManagerчерез method injection. Хуки базового класса публичные.
Хочешь 3D-превью для локализованных мешей? Waveform для аудиоклипов? Имплементируй IAssetPreviewRenderer где угодно в Editor-коде — он будет автоматически обнаружен через TypeCache.
using SimplyLocalize.Editor.AssetPreviews;
using UnityEditor;
using UnityEngine;
public class MeshPreviewRenderer : IAssetPreviewRenderer
{
public int Priority => 10;
public bool CanRender(Object asset) => asset is Mesh;
public void DrawPreview(Rect rect, Object asset)
{
var mesh = (Mesh)asset;
var preview = AssetPreview.GetAssetPreview(mesh);
if (preview != null)
GUI.DrawTexture(rect, preview, ScaleMode.ScaleToFit);
}
}Более высокий Priority побеждает если несколько рендереров могут отрисовать один тип. Встроенные sprite/texture/audio рендереры используют priority 10, так что любая пользовательская реализация с priority 11+ их перекроет.
Dropdown в табе Assets строится из обнаруженных реализаций IAssetTypeFilter плюс автогенерируемых фильтров для всех типов которые есть в твоих таблицах. Обычно своих писать не нужно — автогенератор использует ObjectNames.NicifyVariableName для display-имён. Но если хочешь имя получше дефолтного (например, "UI Icons" вместо "Sprite") — реализуй интерфейс:
using System;
using System.Collections.Generic;
using SimplyLocalize;
using SimplyLocalize.Editor.AssetFilters;
using UnityEngine;
public class UIIconFilter : IAssetTypeFilter
{
public string DisplayName => "UI Icons";
public int Order => 5;
public Type AcceptedFieldType => typeof(Sprite);
public bool MatchesKey(string key, IReadOnlyDictionary<string, LocalizationAssetTable> tables)
{
if (!key.StartsWith("ui/icons/")) return false;
foreach (var t in tables.Values)
{
if (t == null) continue;
var a = t.Get(key);
if (a == null) continue;
if (a is Sprite) return true;
}
return true; // показываем неназначенные ключи чтобы не пропадали
}
}Добавь свой таб в окно локализации:
using SimplyLocalize.Editor;
using SimplyLocalize.Editor.Windows.Tabs;
using UnityEngine.UIElements;
[LocalizationEditorTab("Glossary", order: 50)]
public class GlossaryTab : IEditorTab
{
public void Build(VisualElement container)
{
container.Add(new Label("Мой кастомный таб!"));
// ... твой UI здесь
}
}Таб появится в баре табов автоматически, отсортированным по order. Регистрация не нужна.
Замени ResourcesDataProvider своей реализацией ILocalizationDataProvider — например, для загрузки переводов из Addressables, удалённого сервера или встроенной БД.
public class MyDataProvider : ILocalizationDataProvider
{
public Dictionary<string, string> LoadTextData(string languageCode) { ... }
public bool HasTextData(string languageCode) { ... }
public List<LocalizationAssetTable> LoadAssetTables(string languageCode) { ... }
}
// При старте
Localization.Initialize(config);
Localization.SetDataProvider(new MyDataProvider());Превращает поле string в поисковый key picker в инспекторе.
using SimplyLocalize;
public class DialogueLine : MonoBehaviour
{
[LocalizationKey] public string key;
}Показывает live-превью разрешённого перевода рядом с полем [LocalizationKey].
[LocalizationKey]
[LocalizationPreview]
public string key;Регистрирует класс как кастомный таб в окне локализации. Класс должен реализовывать IEditorTab.
Если обновляешься с более ранней версии Simply Localize, изменилось следующее:
| v1 | v2 |
|---|---|
Один localization.json со всеми языками |
Папка на язык, несколько JSON-файлов |
LocalizationText |
LocalizedText |
FormattableLocalizationText |
FormattableLocalizedText |
LocalizationImage |
LocalizedSprite |
Localization.SetLocalization("ru") |
Localization.SetLanguage("ru") |
text.TranslateByKey("key") |
Задаётся в Inspector или через component.Key = "key" |
text.SetValue(param) |
component.SetArgs(param) / SetParam(name, value) на FormattableLocalizedText |
Localization.GetSprite(key) |
Localization.GetAsset<Sprite>(key) |
Localization.GetAudio(key) |
Localization.GetAsset<AudioClip>(key) |
Per-language флаги hasText / hasSprites |
Автоматически — сканируется из реальных таблиц |
Миграция не автоматическая. Нужно:
- Реорганизовать JSON-данные в per-language папки
- Заменить старые ссылки на компоненты в сценах/префабах
- Заменить
SetLocalization→SetLanguageв коде - Заменить
GetSprite/GetAudioнаGetAsset<T>
MIT — см. LICENSE.txt.
Issues и pull requests приветствуются на github.com/RenKOFFF/Simply-Localize-Localization-System-for-Unity.





