diff --git a/js/i18n.js b/js/i18n.js
index 76ce246..06dc9a2 100644
--- a/js/i18n.js
+++ b/js/i18n.js
@@ -2,14 +2,15 @@
// Only display names live here; full strings are lazy-loaded per language.
export const TRANSLATIONS = {
- en: { 'lang.name': 'English' },
de: { 'lang.name': 'Deutsch' },
- it: { 'lang.name': 'Italiano' },
+ en: { 'lang.name': 'English' },
es: { 'lang.name': 'Español (beta)' },
- pt: { 'lang.name': 'Português (beta)' },
fr: { 'lang.name': 'Français' },
+ it: { 'lang.name': 'Italiano' },
ja: { 'lang.name': '日本語 (beta)' },
ko: { 'lang.name': '한국어' },
+ pt: { 'lang.name': 'Português (beta)' },
+ ru: { 'lang.name': 'Русский' }
};
// ── Module state ──────────────────────────────────────────────────────────────
diff --git a/js/i18n/ru.js b/js/i18n/ru.js
new file mode 100644
index 0000000..56be22b
--- /dev/null
+++ b/js/i18n/ru.js
@@ -0,0 +1,145 @@
+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": "Карта не выбрана",
+ "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.amplitude": "Амплитуда",
+ "labels.seamBlend": "Сглаживание шва ⓘ",
+ "tooltips.seamBlend": "Смягчает жёсткий шов в местах стыка граней проекции. Эффективно для режимов Cubic и Cylindrical.",
+ "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": "Нажмите на грань, чтобы ориентировать её вниз на стол 3D-принтера",
+ "progress.subdividingPreview": "Подготовка предпросмотра…",
+ "warnings.amplitudeOverlap": "⚠ Амплитуда превышает 10% от наименьшего размера модели — в экспортируемом STL возможны пересечения геометрии.",
+ "sections.export": "Экспорт ⓘ",
+ "tooltips.export": "Меньшая длина ребра = более детализированный рельеф. Затем результат упрощается до лимита треугольников.",
+ "labels.resolution": "Разрешение",
+ "tooltips.resolution": "Рёбра длиннее этого значения будут разбиты при экспорте",
+ "labels.outputTriangles": "Треугольники на выходе",
+ "tooltips.outputTriangles": "Сетка сначала полностью разбивается, затем упрощается до этого числа",
+ "warnings.safetyCapHit": "⚠ Во время разбиения достигнут безопасный лимит в 20 млн треугольников — результат может оказаться грубее, чем заданная длина ребра.",
+ "ui.exportStl": "Экспорт STL",
+ "progress.subdividing": "Разбиение сетки…",
+ "progress.refining": "Уточнение: {cur} треугольников, самое длинное ребро {edge}",
+ "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.",
+ "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 Germany",
+ "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, Germany.",
+ "imprint.privacyHosting": "Этот сайт размещён на GitHub Pages (GitHub Inc. / Microsoft Corp., 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA). При посещении сайта GitHub может обрабатывать ваш IP-адрес в серверных журналах. Правовое основание: Art. 6(1)(f) DSGVO (законный интерес в предоставлении сайта). См. Заявление о конфиденциальности GitHub.",
+ "imprint.privacyLocal": "Этот инструмент сохраняет пользовательские настройки (язык, тема) в localStorage браузера. Эти данные не покидают ваше устройство и не передаются на сервер.",
+ "imprint.privacyNoCookies": "Этот сайт не использует cookies, аналитику или какие-либо технологии отслеживания.",
+ "imprint.privacyExternal": "На сайте есть ссылки на внешние ресурсы (например, CNCKitchen.STORE, PayPal). У этих сайтов свои политики конфиденциальности, на которые мы не влияем.",
+ "imprint.privacyRights": "В соответствии с GDPR у вас есть право на доступ, исправление, удаление, ограничение обработки, переносимость данных, а также право подать жалобу в надзорный орган.",
+ "sponsor.title": "Спасибо, что используете BumpMesh от CNC Kitchen!",
+ "sponsor.body": "Этот инструмент предоставляется совершенно бесплатно компанией CNC Kitchen. Пока ваш STL обрабатывается, загляните в магазин, который помогает нам и дальше делать для вас классные вещи.",
+ "sponsor.visitStore": "🛒 Перейти в CNCKitchen.STORE",
+ "sponsor.donate": "💙 Пожертвовать через PayPal",
+ "sponsor.dontShow": "Больше не показывать",
+ "sponsor.closeAndContinue": "Закрыть и продолжить",
+ "cta.store": "Поддержите проект! Загляните в CNCKitchen.STORE или сделайте пожертвование через PayPal",
+ "cta.storeDismiss": "Закрыть",
+ "alerts.loadFailed": "Не удалось загрузить модель: {msg}",
+ "alerts.exportFailed": "Экспорт не удался: {msg}",
+ "alerts.fileTooLarge": "Файл слишком большой ({size} МБ). Максимум: {max} МБ."
+};
\ No newline at end of file