Локализация интерфейса

Локализация интерфейса в CesiumJS представляет собой набор практик и архитектурных решений, направленных на адаптацию текстовых элементов, форматов отображения данных и пользовательских сообщений под различные языки и региональные настройки. В отличие от классических UI-фреймворков, здесь отсутствует единая встроенная система интернационализации, поэтому локализация реализуется на уровне приложения и расширений над сценой.

Основная сложность заключается в том, что интерфейс Cesium состоит из нескольких слоёв:

  • системные компоненты Viewer (панели, кнопки, тултипы);
  • визуальные элементы сцены (подписи, labels, billboards);
  • служебные сообщения (ошибки загрузки, кредиты, атрибуция);
  • пользовательские UI-слои (меню, формы, панели управления).

Каждый слой требует отдельного подхода к переводу. Центрального словаря интерфейсных строк нет, поэтому применяется модель внешних словарей и функций форматирования.


Базовая стратегия: словари локализации

На практике используется структура словаря ключ–значение:

const locales = {
  ru: {
    zoomIn: "Приблизить",
    zoomOut: "Отдалить",
    home: "Начальный вид",
    layers: "Слои",
    search: "Поиск",
  },
  en: {
    zoomIn: "Zoom in",
    zoomOut: "Zoom out",
    home: "Home",
    layers: "Layers",
    search: "Search",
  }
};

Далее слой UI получает строки через функцию:

function t(key, locale = "en") {
  return locales[locale][key] || key;
}

Такой подход обеспечивает независимость логики Cesium от конкретного языка.


Локализация встроенного Viewer UI

В CesiumJS стандартный Viewer генерирует элементы управления динамически. Это означает, что изменение языка требует модификации DOM после инициализации.

const viewer = new Cesium.Viewer("cesiumContainer");

viewer.homeButton.container.title = t("home", "ru");
viewer.fullscreenButton.container.title = "Полноэкранный режим";
viewer.sceneModePicker.container.title = "Режим сцены";

Однако прямое изменение DOM не всегда стабильно, так как обновления Cesium могут пересоздавать элементы управления.


Перехват и переопределение создания UI

Более устойчивый подход заключается в создании Viewer без стандартного UI и последующем построении собственного интерфейса:

const viewer = new Cesium.Viewer("cesiumContainer", {
  animation: false,
  timeline: false,
  baseLayerPicker: false,
  geocoder: false,
  homeButton: false,
  sceneModePicker: false,
  navigationHelpButton: false,
  fullscreenButton: false
});

Далее UI создаётся вручную:

const button = document.createElement("button");
button.innerText = t("zoomIn", "ru");

button.addEventListener("click", () => {
  viewer.camera.zoomIn(1000);
});

document.body.appendChild(button);

Такой подход полностью отделяет интерфейс от внутренней логики Cesium.


Локализация числовых форматов и координат

Важной частью интерфейса являются координаты, высоты, расстояния и временные метки. Для их форматирования используется стандарт Intl:

const numberFormatter = new Intl.NumberFormat("ru-RU", {
  maximumFractionDigits: 2
});

const formattedHeight = numberFormatter.format(1523.456); // "1 523,46"

Для координат:

function formatLonLat(value, locale) {
  return new Intl.NumberFormat(locale, {
    minimumFractionDigits: 4,
    maximumFractionDigits: 4
  }).format(value);
}

Локализация времени и астрономических данных

Cesium активно работает со временем (JulianDate, clock, timeline). Для отображения пользователю требуется преобразование в локальное время:

const date = Cesium.JulianDate.toDate(viewer.clock.currentTime);

const formatted = new Intl.DateTimeFormat("ru-RU", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "2-digit",
  minute: "2-digit"
}).format(date);

Это особенно важно для таймлайна и анализа спутниковых данных.


Локализация кредитов и атрибуции

Система CreditDisplay в CesiumJS отображает текстовые атрибуции провайдеров данных. Эти строки часто приходят из внешних источников и требуют постобработки.

viewer.creditDisplay.addDefaultCredit(new Cesium.Credit("Данные © OpenStreetMap", true));

Если требуется мультиязычность:

const creditText = {
  ru: "Данные © OpenStreetMap",
  en: "Data © OpenStreetMap"
};

viewer.creditDisplay.addDefaultCredit(
  new Cesium.Credit(creditText["ru"], true)
);

Локализация всплывающих подсказок и инструментов

Tooltip-строки являются частью пользовательского слоя и обычно заменяются вручную:

function setTooltips(locale) {
  document.querySelectorAll("[data-tooltip]").forEach(el => {
    const key = el.getAttribute("data-tooltip");
    el.title = t(key, locale);
  });
}

HTML-структура:

<button data-tooltip="zoomIn"></button>

Обработка направленности текста (LTR / RTL)

Хотя Cesium сам по себе не управляет направлением текста, интерфейс может требовать поддержки языков с письмом справа налево:

document.documentElement.setAttribute("dir", "rtl");
document.documentElement.lang = "ar";

Для корректного отображения UI важно учитывать:

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

Локализация сообщений об ошибках загрузки

Ошибки загрузки ресурсов (tiles, imagery, terrain) обычно перехватываются через события:

viewer.scene.globe.tileLoadProgressEvent.addEventListener((count) => {
  if (count === 0) {
    console.log(t("tilesLoaded", "ru"));
  }
});

Для расширенной обработки:

try {
  const tileset = new Cesium.Cesium3DTileset({ url: "model.json" });
  viewer.scene.primitives.add(tileset);
} catch (e) {
  showError(t("loadError", "ru"));
}

Локализация геокодера и поиска

Стандартный Geocoder в Viewer требует замены для мультиязычного поведения:

const viewer = new Cesium.Viewer("cesiumContainer", {
  geocoder: false
});

Далее подключается кастомный поиск:

async function search(query, locale) {
  const response = await fetch(`/api/search?q=${query}&lang=${locale}`);
  return response.json();
}

Результаты отображаются уже с локализованными подписями.


Локализация слоёв и названий объектов

Названия тайловых слоёв и картографических данных часто приходят из внешних провайдеров. Для их адаптации используется слой отображения:

const layerNames = {
  streets: { ru: "Улицы", en: "Streets" },
  satellite: { ru: "Спутник", en: "Satellite" }
};

function getLayerName(id, locale) {
  return layerNames[id][locale];
}

Динамическое переключение языка

Переключение языка в рантайме требует централизованного обновления всех UI-компонентов:

let currentLocale = "en";

function setLocale(locale) {
  currentLocale = locale;

  updateUITexts();
  updateTooltips(locale);
  updateCredits(locale);
}

Ключевая проблема заключается в том, что Cesium не перерисовывает интерфейс автоматически, поэтому требуется ручная синхронизация всех слоёв.


Интеграция с системами i18n

При использовании внешних библиотек (например, i18next) Cesium выступает как визуальный контейнер:

i18next.init({
  lng: "ru",
  resources: {
    ru: {
      translation: {
        zoomIn: "Приблизить"
      }
    }
  }
});

button.innerText = i18next.t("zoomIn");

Такой подход позволяет централизовать локализацию вне Cesium-логики.


Особенности производственной интеграции

В сложных геоинформационных системах локализация затрагивает:

  • 3D-подписи объектов (LabelCollection);
  • подписи тайловых слоёв;
  • интерфейсы анализа данных;
  • временные шкалы спутниковых орбит.

Label-объекты требуют отдельной обработки текста:

const label = viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(90, 45),
  label: {
    text: t("cityLabel", "ru")
  }
});

При смене языка необходимо пересоздавать или обновлять все label-компоненты.


Производительность при локализации

Частая ошибка — обновление UI через массовый DOM-рефлоу. В системах на CesiumJS рекомендуется:

  • минимизировать прямой доступ к DOM Viewer;
  • хранить тексты в реактивном состоянии;
  • обновлять только изменённые элементы;
  • избегать пересоздания Viewer при смене языка.

Оптимальная модель — разделение сценической логики и UI-слоя, где Cesium отвечает только за визуализацию, а локализация управляется внешним контроллером.