diff --git a/js/i18n.js b/js/i18n.js
index 53d9abd..c909ae0 100644
--- a/js/i18n.js
+++ b/js/i18n.js
@@ -10,6 +10,7 @@ export const TRANSLATIONS = {
fr: { 'lang.name': 'Français' },
ja: { 'lang.name': '日本語' },
ko: { 'lang.name': '한국어' },
+ ru: { 'lang.name': 'Русский' },
};
// ── Module state ──────────────────────────────────────────────────────────────
diff --git a/js/i18n/ru.js b/js/i18n/ru.js
new file mode 100644
index 0000000..805827a
--- /dev/null
+++ b/js/i18n/ru.js
@@ -0,0 +1,219 @@
+export default {
+ "theme.dark": "Тёмная тема",
+ "theme.light": "Светлая тема",
+ "theme.toggleTitle": "Переключить светлый / тёмный режим",
+ "theme.toggleAriaLabel": "Переключить светлый/тёмный режим",
+ "dropHint.text": "Перетащите файл .stl, .obj или .3mf сюда или ",
+ "ui.wireframe": "Каркас",
+ "ui.perspective": "Перспективный вид",
+ "ui.controlsHint": "Перетаскивание левой кнопкой: орбита · Правой кнопкой: панорама · Колесико: масштаб",
+ "ui.meshInfo": "{n} треугольников · {mb} МБ · {sx} × {sy} × {sz} мм",
+ "ui.loadStl": "Загрузить модель…",
+ "ui.localProcessingNote": "Вся обработка выполняется локально в вашем браузере — данные не загружаются.",
+ "sections.displacementMap": "Карта смещения",
+ "ui.uploadCustomMap": "Загрузить пользовательскую карту",
+ "ui.noMapSelected": "Карта не выбрана",
+ "ui.customMap": "Пользовательская карта",
+ "ui.removeCustomMap": "Удалить пользовательскую карту",
+ "ui.loadingTextures": "Загрузка текстур…",
+ "sections.projection": "Проекция",
+ "labels.mode": "Режим",
+ "projection.triplanar": "Трипланарная",
+ "projection.cubic": "Кубическая (Box)",
+ "projection.cylindrical": "Цилиндрическая",
+ "projection.spherical": "Сферическая",
+ "projection.planarXY": "Плоская XY",
+ "projection.planarXZ": "Плоская XZ",
+ "projection.planarYZ": "Плоская YZ",
+ "sections.transform": "Трансформация",
+ "labels.scaleU": "Масштаб U",
+ "labels.scaleV": "Масштаб V",
+ "labels.offsetU": "Смещение U",
+ "labels.offsetV": "Смещение V",
+ "labels.rotation": "Поворот",
+ "tooltips.proportionalScaling": "Пропорциональное масштабирование (U = V)",
+ "tooltips.proportionalScalingAria": "Пропорциональное масштабирование (U = V)",
+ "sections.displacement": "Глубина текстуры",
+ "labels.textureHeight": "Высота текстуры (мм)",
+ "labels.invertDisplacement": "Инвертировать (вдавливать вместо выдавливания)",
+ "labels.seamBlend": "Смягчение шва ⓘ",
+ "tooltips.seamBlend": "Смягчает резкий шов на стыке граней проекции. Эффективно для кубического и цилиндрического режимов.",
+ "labels.transitionSmoothing": "Сглаживание перехода ⓘ",
+ "tooltips.transitionSmoothing": "Ширина зоны смешивания возле краёв шва. Меньшие значения сохраняют переходы близко к шву; большие — смешивают более широкую полосу.",
+ "labels.textureSmoothing": "Сглаживание текстуры ⓘ",
+ "tooltips.textureSmoothing": "Применяет размытие по Гауссу к карте смещения. Большие значения дают более мягкие, плавные детали поверхности. 0 = выключено.",
+ "labels.capAngle": "Угол крышки ⓘ",
+ "tooltips.capAngle": "Угол (в градусах) от вертикали, при котором включается проекция верхней/нижней крышки. Меньшие значения ограничивают проекцию крышки почти плоскими гранями.",
+ "sections.masking": "Маскирование",
+ "sections.maskAngles": "По углу ⓘ",
+ "tooltips.maskAngles": "0° = без маскирования. Поверхности в пределах этого угла от горизонтали не будут текстурированы.",
+ "labels.bottomFaces": "Нижние грани",
+ "tooltips.bottomFaces": "Подавить текстуру на поверхностях, обращённых вниз, в пределах этого угла от горизонтали",
+ "labels.topFaces": "Верхние грани",
+ "tooltips.topFaces": "Подавить текстуру на поверхностях, обращённых вверх, в пределах этого угла от горизонтали",
+ "sections.surfaceMasking": "По поверхности ⓘ",
+ "sections.surfaceSelection": "Выбор поверхности",
+ "tooltips.surfaceMasking": "Маскируйте поверхности, чтобы контролировать, какие области получат смещение.",
+ "tooltips.surfaceSelection": "Выбранные поверхности отображаются зелёным и будут единственными, которые получат смещение при экспорте.",
+ "excl.modeExclude": "Исключить",
+ "excl.modeExcludeTitle": "Режим исключения: закрашенные поверхности не будут получать смещение текстуры",
+ "excl.modeIncludeOnly": "Только включить",
+ "excl.modeIncludeOnlyTitle": "Режим «только включить»: только закрашенные поверхности будут получать смещение текстуры",
+ "excl.toolBrush": "Кисть",
+ "excl.toolBrushTitle": "Кисть: закрасьте треугольники, чтобы исключить их",
+ "excl.toolFill": "Заливка",
+ "excl.toolFillTitle": "Заливка ведром: заливка поверхности до порогового угла",
+ "excl.shiftHint": "Удерживайте Shift для стирания",
+ "labels.type": "Тип",
+ "brushType.single": "Одиночный",
+ "brushType.circle": "Круг",
+ "labels.size": "Размер",
+ "labels.maxAngle": "Макс. угол",
+ "tooltips.maxAngle": "Максимальный двугранный угол между соседними треугольниками, через который может перейти заливка",
+ "ui.clearAll": "Очистить всё",
+ "excl.initExcluded": "0 граней замаскировано",
+ "excl.faceExcluded": "{n} грань замаскирована",
+ "excl.facesExcluded": "{n} граней замаскировано",
+ "excl.faceSelected": "{n} грань выбрана",
+ "excl.facesSelected": "{n} граней выбрано",
+ "excl.hintExclude": "Замаскированные поверхности отображаются оранжевым и не будут получать смещение при экспорте.",
+ "excl.hintInclude": "Выбранные поверхности отображаются зелёным и будут единственными, которые получат смещение при экспорте.",
+ "precision.label": "Точность (Beta) ⓘ",
+ "precision.labelTitle": "Подразделить сетку в фоне, чтобы кисть выбирала с более мелкой гранулярностью",
+ "precision.outdated": "⚠ Устарело",
+ "precision.refreshTitle": "Повторно подразделить сетку в соответствии с текущим размером кисти",
+ "precision.triCount": "{n} △",
+ "precision.refining": "Уточнение…",
+ "precision.warningBody": "Примерно ~{n} треугольников. Это может замедлить ваш браузер. Продолжить?",
+ "labels.boundaryFalloff": "Плавная маска ⓘ",
+ "tooltips.boundaryFalloff": "Постепенно уменьшает смещение до нуля возле замаскированных границ, предотвращая перекрытие треугольников на стыке текстурированных и нетекстурированных областей.",
+ "labels.symmetricDisplacement": "Симметричное смещение ⓘ",
+ "tooltips.symmetricDisplacement": "Когда включено, 50% серый = нет смещения; белый выдавливает наружу, чёрный вдавливает внутрь. Сохраняет объём детали примерно постоянным.",
+ "labels.displacementPreview": "3D предпросмотр ⓘ",
+ "tooltips.displacementPreview": "Подразделяет сетку и смещает вершины в реальном времени, чтобы вы могли оценить фактическую глубину. Требует ресурсов GPU на сложных моделях.",
+ "ui.placeOnFace": "Разместить на грани",
+ "ui.placeOnFaceTitle": "Нажмите на грань, чтобы сориентировать её вниз на рабочую платформу",
+ "ui.rotate": "Повернуть",
+ "ui.rotateTitle": "Вручную повернуть модель вокруг осей X, Y, Z",
+ "ui.rotateApply": "Применить",
+ "ui.rotateReset": "Сбросить",
+ "progress.subdividingPreview": "Подготовка предпросмотра…",
+ "warnings.textureHeightOverlap": "⚠ Высота текстуры превышает 10% наименьшего размера модели — возможны перекрытия геометрии в экспортированном STL.",
+ "sections.advancedFeatures": "Расширенные / бета-функции ⓘ",
+ "tooltips.advancedFeatures": "Экспериментальные инструменты — поведение может меняться между версиями.",
+ "ui.advancedBlurb": "Экспериментальные инструменты. Поведение может меняться между версиями.",
+ "ui.bakeTexturesHeading": "Запекание текстур",
+ "ui.bakeTexturesDesc": "Применить текущую текстуру к сетке и загрузить результат в качестве рабочей модели, чтобы продолжить редактирование.",
+ "ui.bakeTextures": "Запечь текстуры",
+ "tooltips.bakeTextures": "Применить текущую текстуру к сетке и перезагрузить результат как рабочую модель.",
+ "ui.bakeMaskNewFaces": "Замаскировать только что запечённые грани от дальнейшего текстурирования",
+ "ui.noDownwardZHeading": "Защита от нависаний",
+ "ui.noDownwardZDesc": "Некоторые текстуры смещают вершины поверхности вниз, создавая новые нависания. При включении вершины никогда не перемещаются в направлении −Z во время текстурирования. Перемещение по X и Y не затрагивается.",
+ "ui.noDownwardZ": "Предотвратить смещение вершин вниз (−Z)",
+ "ui.smoothBottomHeading": "Сгладить дно",
+ "ui.smoothBottomDesc": "Привязывает любую вершину в пределах 0.1 мм от нижней плоскости к ней после текстурирования. Сохраняет поверхность контакта с платформой идеально плоской, чтобы слайсеры не затеняли мелкие перепады высот на нижних участках.",
+ "ui.smoothBottom": "Привязать вершины, близкие к нижней плоскости, к нижней плоскости",
+ "ui.enableMeshRegularization": "Включить регуляризацию сетки",
+ "alerts.bakeFailed": "Запекание не удалось: {msg}",
+ "progress.finalizing": "Завершение…",
+ "sections.export": "Экспорт ⓘ",
+ "tooltips.export": "Меньшая длина ребра = более мелкая детализация смещения. Затем результат децимируется до лимита треугольников.",
+ "labels.resolution": "Разрешение (мм)",
+ "tooltips.resolution": "Рёбра длиннее этого значения будут разделены при экспорте",
+ "warnings.resolutionTooCoarse": "⚠ Разрешение грубее 1/100 диагонали ограничивающего объёма модели — мелкие детали будут потеряны. Уменьшите значение для более точного результата.",
+ "labels.smartRes": "Предложенные значения (измените по вкусу)",
+ "tooltips.smartRes": "Устанавливает Разрешение и Макс. треугольники на основе детализации активной текстуры, площади поверхности модели и амплитуды смещения.",
+ "ui.smartResInfo": "Smart: {edge} мм · макс {tris} тр · PPE {ppe} · пиксель {pix} мм · поверхность {area} см²",
+ "ui.smartResBudgetCapped": "Ограничено по памяти",
+ "labels.outputTriangles": "Выходные треугольники",
+ "tooltips.outputTriangles": "Сетка сначала полностью подразделяется, затем децимируется до этого количества",
+ "warnings.safetyCapHit": "⚠ Достигнут предохранительный лимит в 16 млн треугольников при подразделении — результат может быть грубее запрошенной длины ребра.",
+ "ui.exportStl": "Экспорт STL",
+ "ui.export3mf": "Экспорт 3MF",
+ "tooltips.exportStl": "Широкая совместимость со всеми слайсерами",
+ "tooltips.export3mf": "Меньший размер файла — может не приниматься всеми слайсерами",
+ "progress.writing3mf": "Запись 3MF…",
+ "progress.subdividing": "Подразделение сетки…",
+ "progress.refining": "Уточнение: {cur} треугольников, самое длинное ребро {edge}",
+ "progress.regularizing": "Регуляризация узких треугольников…",
+ "progress.applyingDisplacement": "Применение смещения к {n} треугольникам…",
+ "progress.displacingVertices": "Смещение вершин…",
+ "progress.decimatingTo": "Упрощение {from} → {to} треугольников…",
+ "progress.decimating": "Упрощение: {cur} → {to} треугольников",
+ "progress.writingStl": "Запись STL…",
+ "progress.done": "Готово!",
+ "progress.processing": "Обработка…",
+ "license.btn": "Лицензия и условия",
+ "license.title": "Лицензия и условия",
+ "license.item1": "Бесплатно для использования в любых целях, включая коммерческие работы (например, текстурирование STL для клиентов или продуктов).",
+ "license.item2": "Упоминание автора приветствуется, но не обязательно при использовании этого инструмента как есть.",
+ "license.item3": "Поддержать этот инструмент? Магазин на CNCKitchen.STORE или отправьте чаевые через PayPal / Ko-fi.",
+ "license.item4": "Этот инструмент предоставляется как есть без каких-либо гарантий. Используйте на свой страх и риск.",
+ "license.item5": "Поддержка не предоставляется. Автор не обязан исправлять ошибки, отвечать на вопросы или обновлять этот инструмент. Тем не менее, сообщения об ошибках и запросы функций всегда приветствуются по адресу texturizer@cnckitchen.com.",
+ "license.item6": "Автор не несёт ответственности за любой ущерб, потерю данных или проблемы, возникшие в результате использования этого инструмента.",
+ "license.item7": "Хотите лицензировать или встроить этот инструмент для своего бизнеса или сайта? Свяжитесь с нами по адресу contact@cnckitchen.com.",
+ "license.item8": "Исходный код доступен на GitHub.",
+ "imprint.btn": "Выходные данные и конфиденциальность",
+ "imprint.title": "Выходные данные и политика конфиденциальности",
+ "imprint.sectionImprint": "Выходные данные (Impressum)",
+ "imprint.info": "CNC Kitchen Stefan Hermann Bahnhofstr. 2 88145 Hergatz Германия",
+ "imprint.contact": "Email: contact@cnckitchen.com Телефон: +49 175 2011824 Номер телефона только для юридических/деловых запросов — не для поддержки.",
+ "imprint.odr": "Платформа онлайн-урегулирования споров ЕС: https://ec.europa.eu/consumers/odr",
+ "imprint.sectionPrivacy": "Политика конфиденциальности (Datenschutzerklärung)",
+ "imprint.privacyIntro": "Ответственная сторона (Verantwortlicher gem. Art. 4 Abs. 7 DSGVO): Stefan Hermann, Bahnhofstr. 2, 88145 Hergatz, Германия.",
+ "imprint.privacyHosting": "Этот сайт размещён на GitHub Pages (GitHub Inc. / Microsoft Corp., 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA). При посещении этого сайта GitHub может обрабатывать ваш IP-адрес в журналах сервера. Правовое основание: ст. 6(1)(f) DSGVO (законный интерес в предоставлении веб-сайта). См. Заявление о конфиденциальности GitHub.",
+ "imprint.privacyLocal": "Этот инструмент хранит пользовательские настройки (язык, тему) в localStorage вашего браузера. Эти данные никогда не покидают ваше устройство и не передаются на сервер.",
+ "imprint.privacyNoCookies": "Этот веб-сайт не использует файлы cookie, аналитику или любые технологии отслеживания.",
+ "imprint.privacyExternal": "Этот сайт содержит ссылки на внешние веб-сайты (например CNCKitchen.STORE, PayPal, Ko-fi). Эти сайты имеют свои собственные политики конфиденциальности, на которые мы не можем повлиять.",
+ "imprint.privacyRights": "В соответствии с GDPR вы имеете право на доступ, исправление, удаление, ограничение обработки, переносимость данных, а также право подать жалобу в надзорный орган.",
+ "sponsor.title": "Спасибо за использование BumpMesh от CNC Kitchen!",
+ "sponsor.body": "Этот инструмент предоставляется абсолютно бесплатно CNC Kitchen. Пока обрабатывается ваш STL, почему бы не заглянуть в магазин, который помогает нам продолжать создавать для вас крутые вещи?",
+ "sponsor.visitStore": "🛒 Посетить CNCKitchen.STORE",
+ "sponsor.donate": "💙 Отправить чаевые через PayPal",
+ "sponsor.donateKofi": "☕ Отправить чаевые через Ko-fi",
+ "sponsor.dontShow": "Больше не показывать это",
+ "sponsor.closeAndContinue": "Закрыть и продолжить",
+ "cta.store": "Поддержать этот инструмент? Магазин на CNCKitchen.STORE или отправьте чаевые через PayPal / Ko-fi",
+ "cta.storeDismiss": "Отклонить",
+ "alerts.loadFailed": "Не удалось загрузить модель: {msg}",
+ "alerts.exportFailed": "Экспорт не удался: {msg}",
+ "alerts.exportOOM": "Экспорт не удался: у модели закончилась память во время подразделения.\n\nЭто происходит, когда сетка требует слишком много треугольников при запрошенном разрешении.\n\nКак исправить:\n\u2022 Увеличьте значение Разрешения (мм), чтобы получить меньше треугольников",
+ "alerts.fileTooLarge": "Файл слишком большой ({size} МБ). Максимум: {max} МБ.",
+ "alerts.degenerateTrianglesRemoved": "{n} недопустимых треугольников (координаты NaN или нулевая площадь) были удалены из сетки при загрузке. Модель по-прежнему будет работать корректно.",
+ "diag.runAdvanced": "Запустить расширенную проверку",
+ "diag.runAdvancedTitle": "Проверить на пересекающиеся и перекрывающиеся треугольники (может занять время на больших сетках)",
+ "diag.running": "Проверка…",
+ "diag.meshOk": "✔ Сетка выглядит хорошо",
+ "diag.openEdges": "{n} открытых рёбер — сетка не герметична",
+ "diag.nonManifoldEdges": "{n} не-многообразие рёбер",
+ "diag.multipleShells": "{n} несвязанных оболочек",
+ "diag.intersectingTris": "{n} пар пересекающихся треугольников",
+ "diag.overlappingTris": "{n} перекрывающихся/дублирующих треугольников",
+ "diag.advancedOk": "✔ Пересечений или наложений не найдено",
+ "diag.recommendFix": "Исправьте эти проблемы в вашем CAD-программном обеспечении, слайсере или онлайн перед текстурированием.",
+ "diag.show": "Показать",
+ "diag.hide": "Скрыть",
+ "header.exportProject": "Сохранить проект (.bumpmesh)",
+ "header.importProject": "Загрузить проект (.bumpmesh)",
+ "header.resetSettings": "Сбросить настройки по умолчанию",
+ "header.undo": "Отменить (Ctrl+Z)",
+ "header.redo": "Повторить (Ctrl+Shift+Z)",
+ "alerts.resetConfirm": "Сбросить все настройки до значений по умолчанию?",
+ "header.exportSettingsLabel": "Настройки",
+ "header.exportModelLabel": "Модель (STL)",
+ "header.exportTextureLabel": "Пользовательская текстура",
+ "header.exportGo": "Сохранить",
+ "alerts.importFailed": "Не удалось загрузить проект: {msg}",
+ "labels.snapSeamless": "Привязать к бесшовной обёртке ⓘ",
+ "tooltips.snapSeamless": "Привязывает масштаб U к целому числу оборотов, чтобы текстура бесшовно оборачивалась вокруг цилиндра.",
+ "labels.cylinderAxis": "Ось цилиндра",
+ "ui.cylinderAutofit": "Автоподгонка",
+ "tooltips.cylinderAutofit": "Подгоняет окружность к обращённым наружу вершинам детали (метод наименьших квадратов).",
+ "ui.cylinderReset": "Сброс",
+ "tooltips.cylinderReset": "Сбрасывает ось к центру ограничивающего объёма и радиусу, полученному из ограничивающего объёма.",
+ "ui.cylinderPanelAria": "Размещение оси цилиндра",
+ "ui.cylinderPanelLabel": "Определить проекцию цилиндра",
+ "ui.cylinderNoModel1": "Загрузите модель для позиционирования",
+ "ui.cylinderNoModel2": "оси цилиндра",
+ "ui.cylinderPanelMinimize": "Свернуть / восстановить"
+};
\ No newline at end of file