536 lines
28 KiB
C#
536 lines
28 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text.RegularExpressions;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using Vanguard.Api.Contracts;
|
||
|
||
namespace Vanguard.UI
|
||
{
|
||
public enum UiLanguage
|
||
{
|
||
Ru,
|
||
En,
|
||
}
|
||
|
||
/// <summary>
|
||
/// Небольшой runtime-словарь интерфейса. Русские строки остаются ключами,
|
||
/// поэтому существующие экраны не нужно переписывать под отдельные id.
|
||
/// </summary>
|
||
public static class Loc
|
||
{
|
||
private const string PreferenceKey = "vanguard.ui.language";
|
||
private static bool _initialized;
|
||
|
||
public static UiLanguage Language { get; private set; } = UiLanguage.Ru;
|
||
public static bool IsEnglish => Language == UiLanguage.En;
|
||
public static event Action LanguageChanged;
|
||
|
||
private static readonly Dictionary<string, string> Exact = new Dictionary<string, string>(StringComparer.Ordinal)
|
||
{
|
||
["ВЫ"] = "YOU",
|
||
["Вы"] = "You",
|
||
["вы"] = "you",
|
||
["да"] = "yes",
|
||
["Бой"] = "Battle",
|
||
["Все"] = "All",
|
||
["нет"] = "no",
|
||
["Вы: "] = "You: ",
|
||
["ДРОП"] = "DROP",
|
||
["Душа"] = "Soul",
|
||
["ДУША"] = "SOUL",
|
||
["Кадр"] = "Frame",
|
||
["Клан"] = "Clan",
|
||
["Пыль"] = "Dust",
|
||
["Ранг"] = "Grade",
|
||
["Рука"] = "Hand",
|
||
["УРОН"] = "DAMAGE",
|
||
[" карт"] = " cards",
|
||
["» за "] = "» for ",
|
||
["В бой"] = "To battle",
|
||
["ВОЙТИ"] = "LOG IN",
|
||
["Выйти"] = "Log out",
|
||
["Кадр "] = "Frame ",
|
||
["Ничья"] = "Draw",
|
||
["Пусто"] = "Empty",
|
||
["Сброс"] = "Reset",
|
||
["Тип: "] = "Type: ",
|
||
["Щит: "] = "Shield: ",
|
||
["цена "] = "cost ",
|
||
[" карт."] = " cards.",
|
||
[" пыли)"] = " dust)",
|
||
["Атака "] = "Attack ",
|
||
["Атака"] = "Attack",
|
||
["Атака с поддержкой: "] = "Boosted attack: ",
|
||
["Активировать способность"] = "Activate ability",
|
||
["Активировать: "] = "Activate: ",
|
||
["Сброс для способности"] = "Discard for ability",
|
||
["Сбросить: "] = "Discard: ",
|
||
["Без сброса"] = "No discard",
|
||
["Guard без сброса"] = "Guard without discard",
|
||
["Perfect Guard: сбросить "] = "Perfect Guard: discard ",
|
||
["Защититься"] = "Guard",
|
||
["Защититься: "] = "Guard: ",
|
||
["Защита"] = "Guard",
|
||
["Не защищаться"] = "Do not guard",
|
||
["Действия"] = "Actions",
|
||
["Переместить"] = "Move",
|
||
["Подробнее"] = "Details",
|
||
["Нет доступных действий"] = "No available actions",
|
||
["Выберите цель атаки."] = "Choose an attack target.",
|
||
["Выберите круг для перемещения."] = "Choose a circle to move to.",
|
||
["В меню"] = "To menu",
|
||
["Готово"] = "Done",
|
||
["Журнал"] = "Log",
|
||
["КОЛОДА"] = "DECK",
|
||
["Кадр ▶"] = "Frame ▶",
|
||
["Клан: "] = "Clan: ",
|
||
["Колода"] = "Deck",
|
||
["Купить"] = "Buy",
|
||
["Отмена"] = "Cancel",
|
||
["Пароль"] = "Password",
|
||
["Победа"] = "Victory",
|
||
["Ранг: "] = "Grade: ",
|
||
["Реплей"] = "Replay",
|
||
["СКРЫТА"] = "HIDDEN",
|
||
["Сервер"] = "Server",
|
||
["Токены"] = "Tokens",
|
||
["Урон: "] = "Damage: ",
|
||
["Фаза: "] = "Phase: ",
|
||
["◀ Кадр"] = "◀ Frame",
|
||
[" · ход "] = " · turn ",
|
||
["1 токен"] = "1 token",
|
||
["\nМатч "] = "\nMatch ",
|
||
["АТАКА "] = "ATTACK ",
|
||
["Атака: "] = "Attack: ",
|
||
["Всего: "] = "Total: ",
|
||
["Готово."] = "Done.",
|
||
["Забрать"] = "Claim",
|
||
["Закрыть"] = "Close",
|
||
["История"] = "History",
|
||
["Карта: "] = "Card: ",
|
||
["Магазин"] = "Store",
|
||
["Победа!"] = "Victory!",
|
||
["Принт: "] = "Printing: ",
|
||
["С ботом"] = "Vs bot",
|
||
["Сдаться"] = "Concede",
|
||
["События"] = "Events",
|
||
["Триггер"] = "Trigger",
|
||
["Удалить"] = "Delete",
|
||
["Фильтры"] = "Filters",
|
||
["← Назад"] = "← Back",
|
||
[" рука "] = " hand ",
|
||
[" (всего "] = " (total ",
|
||
[" · пыль "] = " · dust ",
|
||
[" · рука "] = " · hand ",
|
||
[" · урон "] = " · damage ",
|
||
[" · шаг: "] = " · step: ",
|
||
[" карт · "] = " cards · ",
|
||
[" против "] = " vs ",
|
||
["Действия"] = "Actions",
|
||
["Кадров: "] = "Frames: ",
|
||
["Колода: "] = "Deck: ",
|
||
["Крафтим…"] = "Crafting…",
|
||
["Купить «"] = "Buy «",
|
||
["Матчей: "] = "Matches: ",
|
||
["Принято."] = "Accepted.",
|
||
["Уже есть"] = "Already owned",
|
||
[" · пыль: "] = " · dust: ",
|
||
[", эффект "] = ", effect ",
|
||
["Все карты"] = "All cards",
|
||
["Выбрать: "] = "Choose: ",
|
||
["Клан: все"] = "Clan: all",
|
||
["Коллекция"] = "Collection",
|
||
["Нет колод"] = "No decks",
|
||
["ПРОТИВНИК"] = "OPPONENT",
|
||
["Поражение"] = "Defeat",
|
||
["Противник"] = "Opponent",
|
||
["РУКА · "] = "HAND · ",
|
||
["Разобрать"] = "Dismantle",
|
||
["Ранг: все"] = "Grade: all",
|
||
["Реплей · "] = "Replay · ",
|
||
["Скрафтить"] = "Craft",
|
||
["Сохранить"] = "Save",
|
||
["Триггер: "] = "Trigger: ",
|
||
["противник"] = "opponent",
|
||
["← В лобби"] = "← To lobby",
|
||
[" · урон "] = " · damage ",
|
||
[" · колода "] = " · deck ",
|
||
[" завершён."] = " finished.",
|
||
["В фазу боя"] = "To battle phase",
|
||
["Имя игрока"] = "Player name",
|
||
["Кадров нет"] = "No frames",
|
||
["Купить за "] = "Buy for ",
|
||
["Маллиган: "] = "Mulligan: ",
|
||
["Мои колоды"] = "My decks",
|
||
["Нет реплея"] = "No replay",
|
||
["Ожидание: "] = "Waiting: ",
|
||
["Поражение."] = "Defeat.",
|
||
["Проверок: "] = "Checks: ",
|
||
["Разбираем…"] = "Dismantling…",
|
||
["Рука пуста"] = "Hand is empty",
|
||
["Сохраняем…"] = "Saving…",
|
||
["Триал-деки"] = "Trial decks",
|
||
[" · решает: "] = " · deciding: ",
|
||
[" уникальных"] = " unique",
|
||
["Активируем "] = "Activating ",
|
||
["Бустер BT01"] = "BT01 Booster",
|
||
["ЗОНА ЗАЩИТЫ"] = "GUARD ZONE",
|
||
["Отклонено ("] = "Rejected (",
|
||
["Поддержка: "] = "Boost: ",
|
||
["Противник: "] = "Opponent: ",
|
||
["Сохранено: "] = "Saved: ",
|
||
["Способности"] = "Abilities",
|
||
["без причины"] = "without reason",
|
||
["в коллекции"] = "in collection",
|
||
["← К истории"] = "← To history",
|
||
["← Коллекция"] = "← Collection",
|
||
[" · Пыль "] = " · Dust ",
|
||
[" · банк "] = " · bank ",
|
||
[" · колода "] = " · deck ",
|
||
[" · в колоде "] = " · in deck ",
|
||
[" · событий: "] = " · events: ",
|
||
[" карт · "] = " cards · ",
|
||
["3–32 символа"] = "3–32 characters",
|
||
["Колода пуста"] = "Deck is empty",
|
||
["Колода: 0/50"] = "Deck: 0/50",
|
||
["Новая колода"] = "New deck",
|
||
["Отправляем: "] = "Sending: ",
|
||
["Разобрать (+"] = "Dismantle (+",
|
||
["Скрафтить (−"] = "Craft (−",
|
||
["Событий нет."] = "No events.",
|
||
["Триггер: все"] = "Trigger: all",
|
||
[" · Редкость: "] = " · Rarity: ",
|
||
[" · в колоде: "] = " · in deck: ",
|
||
["Атака отбита."] = "Attack was guarded.",
|
||
["Атака прошла."] = "Attack went through.",
|
||
["Бустер открыт"] = "Booster opened",
|
||
["Закончить ход"] = "End turn",
|
||
["Матч завершён"] = "Match finished",
|
||
["Подключаемся…"] = "Connecting…",
|
||
["Способность #"] = "Ability #",
|
||
[" · Токены "] = " · Tokens ",
|
||
[" · побед: "] = " · wins: ",
|
||
["8–128 символов"] = "8–128 characters",
|
||
["Бустер открыт!"] = "Booster opened!",
|
||
["В коллекции: ×"] = "In collection: ×",
|
||
["История матчей"] = "Match history",
|
||
["Личный кабинет"] = "Profile",
|
||
["Матч завершён."] = "Match finished.",
|
||
["Отменить поиск"] = "Cancel search",
|
||
["Создать колоду"] = "Create deck",
|
||
["Триггер: сила "] = "Trigger: power ",
|
||
["Ищем соперника…"] = "Searching for opponent…",
|
||
["Колода удалена."] = "Deck deleted.",
|
||
["Название колоды"] = "Deck name",
|
||
["Повторить поиск"] = "Search again",
|
||
["Пропустить Ride"] = "Skip Ride",
|
||
["СОЗДАТЬ АККАУНТ"] = "CREATE ACCOUNT",
|
||
["Стартовый набор"] = "Starter set",
|
||
["Удалить колоду?"] = "Delete deck?",
|
||
["Уже в коллекции"] = "Already in collection",
|
||
["Бустер открыт · "] = "Booster opened · ",
|
||
["Готовим очередь…"] = "Preparing queue…",
|
||
["Карта разобрана."] = "Card dismantled.",
|
||
["Карта скрафчена."] = "Card crafted.",
|
||
["Матчей пока нет."] = "No matches yet.",
|
||
["Нельзя разобрать"] = "Cannot dismantle",
|
||
["Нельзя скрафтить"] = "Cannot craft",
|
||
["Переподключиться"] = "Reconnect",
|
||
["Поиск противника"] = "Opponent search",
|
||
["Пропустить Guard"] = "Skip Guard",
|
||
["Создаём аккаунт…"] = "Creating account…",
|
||
["Выберите покупку."] = "Choose a purchase.",
|
||
["Выбрать бесплатно"] = "Choose for free",
|
||
["Выбрать вариант #"] = "Choose option #",
|
||
["Выбрать триггер #"] = "Choose trigger #",
|
||
["Выйти из аккаунта"] = "Log out",
|
||
["Дека уже получена"] = "Deck already claimed",
|
||
["Загружаем реплей…"] = "Loading replay…",
|
||
["Купить за 1 токен"] = "Buy for 1 token",
|
||
["Очередь закрыта: "] = "Queue closed: ",
|
||
["Партия завершена."] = "Game over.",
|
||
["Подтвердить сдачу"] = "Confirm concede",
|
||
["Профиль обновлён."] = "Profile updated.",
|
||
["Фильтры коллекции"] = "Collection filters",
|
||
["Выбрать триал-деку"] = "Choose trial deck",
|
||
["Выполняем покупку…"] = "Processing purchase…",
|
||
["Загружаем историю…"] = "Loading history…",
|
||
["Загружаем магазин…"] = "Loading store…",
|
||
["Закончить маллиган"] = "Finish mulligan",
|
||
["Карт в коллекции: "] = "Cards in collection: ",
|
||
["Купить за 100 Gold"] = "Buy for 100 Gold",
|
||
["Обновляем профиль…"] = "Updating profile…",
|
||
["Повторить загрузку"] = "Retry loading",
|
||
["Подтвердите покупку"] = "Confirm purchase",
|
||
["Покупка не удалась."] = "Purchase failed.",
|
||
["Пропустить анимации"] = "Skip animations",
|
||
["Скрыть все действия"] = "Hide all actions",
|
||
[" с · окно MMR: ±"] = " with · MMR window: ±",
|
||
["Онбординг завершён: "] = "Onboarding complete: ",
|
||
["Повтор уже применён."] = "Replay already applied.",
|
||
["В колоде уже 50 карт."] = "The deck already has 50 cards.",
|
||
["Загружаем триал-деки…"] = "Loading trial decks…",
|
||
["Подключаемся к матчу…"] = "Connecting to match…",
|
||
["Показать все действия"] = "Show all actions",
|
||
["Получаем новый тикет…"] = "Getting a new ticket…",
|
||
["Применить способность"] = "Activate ability",
|
||
["Сервер вернул ошибку "] = "Server returned error ",
|
||
["Соединение потеряно: "] = "Connection lost: ",
|
||
[" · защита между полями"] = " · guard between fields",
|
||
[". Выберите цель атаки."] = ". Choose an attack target.",
|
||
["Выберите цель на поле."] = "Choose a target on the field.",
|
||
["Колод пока нет. Пыль: "] = "No decks yet. Dust: ",
|
||
["Не удалось связаться: "] = "Could not connect: ",
|
||
["Пропустить способность"] = "Skip ability",
|
||
["Сервер не выдал тикет."] = "Server did not issue a ticket.",
|
||
["Триал-деки недоступны."] = "Trial decks are unavailable.",
|
||
["Ничья или матч прерван."] = "Draw or match interrupted.",
|
||
["Открыть бустер BT01 за "] = "Open BT01 booster for ",
|
||
["» добавлена в коллекцию."] = "» added to collection.",
|
||
["Не удалось создать матч."] = "Could not create a match.",
|
||
["Сейчас решает противник…"] = "Opponent is deciding…",
|
||
["Не удалось подключиться: "] = "Could not connect: ",
|
||
["Ошибка отправки интента: "] = "Failed to send intent: ",
|
||
["Сначала сохраните колоду."] = "Save the deck first.",
|
||
["RE:MASTER · СЕТЕВОЙ КЛИЕНТ"] = "RE:MASTER · NETWORK CLIENT",
|
||
["Матч найден! Подключаемся…"] = "Match found! Connecting…",
|
||
["Не удалось удалить колоду."] = "Could not delete the deck.",
|
||
["Готовим тренировочный матч…"] = "Preparing practice match…",
|
||
["Не удалось разобрать карту."] = "Could not dismantle the card.",
|
||
["Не удалось скрафтить карту."] = "Could not craft the card.",
|
||
["Переподключение не удалось."] = "Reconnect failed.",
|
||
["Состояние кадра недоступно."] = "Frame state unavailable.",
|
||
["Classic Ranked · скрытый MMR"] = "Classic Ranked · hidden MMR",
|
||
["Введите имя игрока и пароль."] = "Enter player name and password.",
|
||
["Не удалось загрузить реплей."] = "Could not load replay.",
|
||
["Не удалось сохранить колоду."] = "Could not save the deck.",
|
||
["Триггер: выберите цель силы."] = "Trigger: choose a power target.",
|
||
[". ПКМ по карте — подробности."] = ". Right-click a card for details.",
|
||
["Загружаем коллекцию и колоды…"] = "Loading collection and decks…",
|
||
["Не удалось загрузить историю."] = "Could not load history.",
|
||
["Не удалось загрузить магазин."] = "Could not load store.",
|
||
["Состав появится после выбора."] = "The lineup appears after selection.",
|
||
[" · ПКМ по карте — подробности."] = " · Right-click a card for details.",
|
||
["Не больше 4 копий одной карты."] = "No more than 4 copies of a card.",
|
||
["Не удалось обновить состояние."] = "Could not refresh state.",
|
||
["Текст способности отсутствует."] = "Ability text is missing.",
|
||
["Не удалось завершить онбординг."] = "Could not complete onboarding.",
|
||
["Не удалось загрузить декбилдер."] = "Could not load deck builder.",
|
||
["Не удалось начать матч с ботом."] = "Could not start a match against the bot.",
|
||
["Триггер: выберите цель эффекта."] = "Trigger: choose an effect target.",
|
||
[".\nВыберите цель атаки или «Отмена»."] = ".\nChoose an attack target or “Cancel”.",
|
||
["Нажмите «Подтвердить сдачу» ещё раз."] = "Press “Confirm concede” again.",
|
||
["Очередь недоступна. Проверьте сервер."] = "Queue unavailable. Check the server.",
|
||
["Сервер не вернул ни одной триал-деки."] = "Server returned no trial decks.",
|
||
["Соединение открыто. Ожидаем состояние…"] = "Connection open. Waiting for state…",
|
||
["В коллекции больше нет свободных копий."] = "No free copies left in the collection.",
|
||
["Выберите деку — выбор делается один раз."] = "Choose a deck — this can be done only once.",
|
||
["\nКолода будет удалена без восстановления."] = "\nThe deck will be deleted permanently.",
|
||
["Нажмите на колоду,\nчтобы открыть редактор."] = "Click a deck\nto open the editor.",
|
||
["ЛКМ — убрать копию,\nПКМ — посмотреть карту."] = "LMB — remove a copy,\nRMB — view the card.",
|
||
["Для этой карты сейчас нет доступного действия."] = "No action is available for this card right now.",
|
||
["Только ваши матчи · реплей доступен участникам"] = "Only your matches · replay available to participants",
|
||
["Не удалось обновить профиль. Сервер недоступен?"] = "Could not update profile. Is the server down?",
|
||
["Не удалось загрузить триал-деки. Сервер недоступен?"] = "Could not load trial decks. Is the server down?",
|
||
["Онбординг не завершён: выберите стартовую триал-деку."] = "Onboarding incomplete: choose a starter trial deck.",
|
||
[" · стенд 192.168.31.158:18080 · localhost:5080"] = " · stand 192.168.31.158:18080 · localhost:5080",
|
||
["Нет связи с сервером. Проверьте адрес и запущен ли сервер."] = "No connection to the server. Check the address and that the server is running.",
|
||
["Выберите цель на поле.\nНажмите «Отмена», чтобы снять выбор."] = "Choose a target on the field.\nPress “Cancel” to clear the selection.",
|
||
["5 карт: 4 C + 1 R+ · duplicate protection · 100 Gold или 1 токен."] = "5 cards: 4 C + 1 R+ · duplicate protection · 100 Gold or 1 token.",
|
||
["Нажмите на карту в руке или на поле.\nДопустимые цели подсветятся."] = "Click a card in hand or on the field.\nValid targets will be highlighted.",
|
||
["Выберите одну триал-деку. 50 карт попадут в коллекцию, плюс 5 токенов на бустеры."] = "Choose one trial deck. 50 cards will be added to your collection, plus 5 tokens for boosters.",
|
||
["Unity блокирует HTTP. Player Settings → Allow downloads over HTTP = Always, затем перезапусти Unity."] = "Unity blocks HTTP. Player Settings → Allow downloads over HTTP = Always, then restart Unity.",
|
||
["VanguardApp не создан. Запусти игру из Boot или через Play в любой сцене клиента."] = "VanguardApp was not created. Start the game from Boot or via Play in any client scene.",
|
||
["Кликните карты, которые вернутся в колоду."] = "Click the cards you want to return to the deck.",
|
||
["К обмену выбрано карт: "] = "Cards selected for mulligan: ",
|
||
["Закрытая карта"] = "Face-down card",
|
||
["Выберите закрытую карту урона для Counter Blast (необязательно)."] = "Choose a face-down damage card for Counter Blast (optional).",
|
||
["Для Counter Blast выбрана карта урона "] = "Damage card selected for Counter Blast: ",
|
||
["УРОН · "] = "DAMAGE · ",
|
||
["Нужно ровно 50 карт и 16 триггеров."] = "The deck needs exactly 50 cards and 16 triggers.",
|
||
["В колоде уже 16 триггеров."] = "The deck already has 16 triggers.",
|
||
[" · триггеры: "] = " · triggers: ",
|
||
};
|
||
|
||
private static readonly KeyValuePair<string, string>[] Phrases = Exact
|
||
.Where(pair => IsPhraseKey(pair.Key))
|
||
.OrderByDescending(pair => pair.Key.Length)
|
||
.ToArray();
|
||
|
||
private static readonly Regex YesWord = new Regex(
|
||
@"\bда\b",
|
||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||
private static readonly Regex NoWord = new Regex(
|
||
@"\bнет\b",
|
||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||
|
||
public static void Initialize()
|
||
{
|
||
if (_initialized)
|
||
return;
|
||
|
||
_initialized = true;
|
||
Language = PlayerPrefs.GetString(PreferenceKey, "ru") == "en"
|
||
? UiLanguage.En
|
||
: UiLanguage.Ru;
|
||
}
|
||
|
||
public static void SetLanguage(UiLanguage language)
|
||
{
|
||
Initialize();
|
||
if (Language == language)
|
||
return;
|
||
|
||
Language = language;
|
||
PlayerPrefs.SetString(PreferenceKey, language == UiLanguage.En ? "en" : "ru");
|
||
PlayerPrefs.Save();
|
||
LanguageChanged?.Invoke();
|
||
}
|
||
|
||
public static void Toggle() =>
|
||
SetLanguage(IsEnglish ? UiLanguage.Ru : UiLanguage.En);
|
||
|
||
public static string Translate(string source)
|
||
{
|
||
if (string.IsNullOrEmpty(source) || !IsEnglish)
|
||
return source;
|
||
|
||
if (Exact.TryGetValue(source, out var exact))
|
||
return exact;
|
||
|
||
var result = source;
|
||
foreach (var pair in Phrases)
|
||
{
|
||
if (result.Contains(pair.Key, StringComparison.Ordinal))
|
||
result = result.Replace(pair.Key, pair.Value, StringComparison.Ordinal);
|
||
}
|
||
|
||
result = YesWord.Replace(result, "yes");
|
||
result = NoWord.Replace(result, "no");
|
||
return result;
|
||
}
|
||
|
||
public static string CardName(CardCatalogItemDto card)
|
||
{
|
||
if (card == null)
|
||
return string.Empty;
|
||
|
||
var localized = IsEnglish ? card.NameEn : card.NameRu;
|
||
return string.IsNullOrWhiteSpace(localized) ? card.NameEn : localized;
|
||
}
|
||
|
||
public static string OtherCardName(CardCatalogItemDto card)
|
||
{
|
||
if (card == null)
|
||
return string.Empty;
|
||
|
||
var other = IsEnglish ? card.NameRu : card.NameEn;
|
||
return string.IsNullOrWhiteSpace(other) ? string.Empty : other;
|
||
}
|
||
|
||
public static string CardText(CardCatalogItemDto card)
|
||
{
|
||
if (card == null)
|
||
return string.Empty;
|
||
|
||
var localized = IsEnglish ? card.TextEn : card.TextRu;
|
||
if (!string.IsNullOrWhiteSpace(localized))
|
||
return localized;
|
||
|
||
return card.TextEn ?? string.Empty;
|
||
}
|
||
|
||
private static bool IsPhraseKey(string key)
|
||
{
|
||
if (key.Length >= 6)
|
||
return true;
|
||
|
||
return key.Any(character =>
|
||
character == ' ' || character == ':' || character == '.' ||
|
||
character == ',' || character == '!' || character == '?' ||
|
||
character == '«' || character == '»' || character == '·' ||
|
||
character == '—' || character == '–' || character == '\n' ||
|
||
character == '(' || character == ')' || character == '±' ||
|
||
character == '←' || character == '→');
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Переводит uGUI-лейбл при смене языка и подхватывает текст, выставленный кодом.
|
||
/// </summary>
|
||
[DisallowMultipleComponent]
|
||
public sealed class LocalizedText : MonoBehaviour
|
||
{
|
||
private Text _text;
|
||
private string _source = string.Empty;
|
||
private string _applied = string.Empty;
|
||
private bool _literal;
|
||
private bool _subscribed;
|
||
|
||
private void Awake() => Subscribe();
|
||
|
||
private void OnDestroy() => Unsubscribe();
|
||
|
||
public void Initialize(string source)
|
||
{
|
||
_text = GetComponent<Text>();
|
||
_source = source ?? string.Empty;
|
||
_literal = false;
|
||
Subscribe();
|
||
ApplyCurrent();
|
||
}
|
||
|
||
private void Subscribe()
|
||
{
|
||
if (_subscribed)
|
||
return;
|
||
|
||
Loc.LanguageChanged += ApplyCurrent;
|
||
_subscribed = true;
|
||
}
|
||
|
||
private void Unsubscribe()
|
||
{
|
||
if (!_subscribed)
|
||
return;
|
||
|
||
Loc.LanguageChanged -= ApplyCurrent;
|
||
_subscribed = false;
|
||
}
|
||
|
||
public void SetLiteral(string value)
|
||
{
|
||
_literal = true;
|
||
_source = value ?? string.Empty;
|
||
_applied = _source;
|
||
if (_text != null)
|
||
_text.text = _source;
|
||
}
|
||
|
||
private void LateUpdate() => ApplyNow();
|
||
|
||
public void ApplyNow()
|
||
{
|
||
if (_literal || _text == null)
|
||
return;
|
||
|
||
if (!string.Equals(_text.text, _applied, StringComparison.Ordinal))
|
||
_source = _text.text;
|
||
|
||
ApplyCurrent();
|
||
}
|
||
|
||
private void ApplyCurrent()
|
||
{
|
||
if (_literal || _text == null)
|
||
return;
|
||
|
||
_applied = Loc.Translate(_source);
|
||
if (!string.Equals(_text.text, _applied, StringComparison.Ordinal))
|
||
_text.text = _applied;
|
||
}
|
||
}
|
||
}
|