7c8b5aeb23
Add per-planet cargo routes (COL/CAP/MAT/EMP) to the inspector with a renderer-driven destination picker (faded out-of-reach planets, cursor-line anchor, hover-highlight) and per-route arrows on the map. The pick-mode primitives are exposed via `MapPickService` so ship-group dispatch in Phase 19/20 can reuse the same surface. Pass A — generic map foundation: - hit-test now sizes the click zone to `pointRadiusPx + slopPx` so the visible disc is always part of the target. - `RendererHandle` gains `onPointerMove`, `onHoverChange`, `setPickMode`, `getPickState`, `getPrimitiveAlpha`, `setExtraPrimitives`, `getPrimitives`. The click dispatcher is centralised: pick-mode swallows clicks atomically so the standard selection consumers do not race against teardown. - `MapPickService` (`lib/map-pick.svelte.ts`) wraps the renderer contract in a promise-shaped `pick(...)`. The in-game shell layout owns the service so sidebar and bottom-sheet inspectors see the same instance. - Debug-surface registry exposes `getMapPrimitives`, `getMapPickState`, `getMapCamera` to e2e specs without spawning a separate debug page after navigation. Pass B — cargo-route feature: - `CargoLoadType`, `setCargoRoute`, `removeCargoRoute` typed variants with `(source, loadType)` collapse rule on the order draft; round-trip through the FBS encoder/decoder. - `GameReport` decodes `routes` and the local player's drive tech for the inline reach formula (40 × drive). `applyOrderOverlay` upserts/drops route entries for valid/submitting/applied commands. - `lib/inspectors/planet/cargo-routes.svelte` renders the four-slot section. `Add` / `Edit` call `MapPickService.pick`, `Remove` emits `removeCargoRoute`. - `map/cargo-routes.ts` builds shaft + arrowhead primitives per cargo type; the map view pushes them through `setExtraPrimitives` so the renderer never re-inits Pixi on route mutations (Pixi 8 doesn't support that on a reused canvas). Docs: - `docs/cargo-routes-ux.md` covers engine semantics + UI map. - `docs/renderer.md` documents pick mode and the debug surface. - `docs/calc-bridge.md` records the Phase 16 reach waiver. - `PLAN.md` rewrites Phase 16 to reflect the foundation + feature split and the decisions baked in (map-driven picker, inline reach, optimistic overlay via `setExtraPrimitives`). Tests: - `tests/map-pick-mode.test.ts` — pure overlay-spec helper. - `tests/map-cargo-routes.test.ts` — `buildCargoRouteLines`. - `tests/inspector-planet-cargo-routes.test.ts` — slot rendering, picker invocation, collapse, cancel, remove. - Extensions to `order-draft`, `submit`, `order-load`, `order-overlay`, `state-binding`, `inspector-planet`, `inspector-overlay`, `game-shell-sidebar`, `game-shell-header`. - `tests/e2e/cargo-routes.spec.ts` — Playwright happy path: add COL, add CAP, remove COL, asserting both the inspector and the arrow count via `__galaxyDebug.getMapPrimitives()`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
199 lines
12 KiB
TypeScript
199 lines
12 KiB
TypeScript
// Russian translation dictionary. The keys are identical to the
|
||
// English dictionary in `en.ts`; the values are the human Russian
|
||
// text. Adding a new key requires updating every locale file in
|
||
// this folder so the `t()` helper does not fall back to English.
|
||
|
||
import type en from "./en";
|
||
|
||
const ru: Record<keyof typeof en, string> = {
|
||
"common.language": "язык",
|
||
"common.loading": "загрузка…",
|
||
"common.browser_not_supported_title": "браузер не поддерживается",
|
||
"common.browser_not_supported_body":
|
||
"Galaxy требует поддержки Ed25519 в WebCrypto. См. список поддерживаемых браузеров.",
|
||
|
||
"login.title": "вход в Galaxy",
|
||
"login.email_label": "электронная почта",
|
||
"login.email_required": "адрес не должен быть пустым",
|
||
"login.send_code": "отправить код",
|
||
"login.sending": "отправляем…",
|
||
"login.code_label": "код",
|
||
"login.code_required": "код не должен быть пустым",
|
||
"login.code_sent_to": "код отправлен на {email}",
|
||
"login.verify": "подтвердить",
|
||
"login.verifying": "проверяем…",
|
||
"login.send_new_code": "отправить новый код",
|
||
"login.change_email": "изменить адрес",
|
||
"login.challenge_expired":
|
||
"запрос устарел, запросите новый код",
|
||
"login.code_expired_or_used":
|
||
"код устарел или уже использован, запросите новый",
|
||
"login.device_key_not_ready":
|
||
"ключ устройства ещё не готов, перезагрузите страницу",
|
||
|
||
"lobby.title": "вы вошли в систему",
|
||
"lobby.device_session_id_label": "идентификатор сессии устройства",
|
||
"lobby.greeting": "здравствуйте, {name}!",
|
||
"lobby.account_loading": "загрузка профиля…",
|
||
"lobby.logout": "выйти",
|
||
"lobby.section.my_games": "мои игры",
|
||
"lobby.section.invitations": "ожидающие приглашения",
|
||
"lobby.section.applications": "мои заявки",
|
||
"lobby.section.public_games": "публичные игры",
|
||
"lobby.section.create": "создать игру",
|
||
"lobby.create_button": "создать новую игру",
|
||
"lobby.my_games.empty": "пока нет игр",
|
||
"lobby.invitations.empty": "приглашений нет",
|
||
"lobby.applications.empty": "заявок нет",
|
||
"lobby.public_games.empty": "публичных игр нет",
|
||
"lobby.invitation.accept": "принять",
|
||
"lobby.invitation.decline": "отклонить",
|
||
"lobby.application.submit": "подать заявку",
|
||
"lobby.application.submit_for": "подать заявку в {name}",
|
||
"lobby.application.race_name_label": "название расы",
|
||
"lobby.application.race_name_required": "название расы не должно быть пустым",
|
||
"lobby.application.cancel": "отмена",
|
||
"lobby.application.submitted": "заявка отправлена, ожидает одобрения",
|
||
"lobby.application.status.pending": "ожидает",
|
||
"lobby.application.status.approved": "одобрена",
|
||
"lobby.application.status.rejected": "отклонена",
|
||
"lobby.application.status.unknown": "{status}",
|
||
"lobby.list_loading": "загрузка…",
|
||
"lobby.create.title": "создание новой игры",
|
||
"lobby.create.game_name_label": "название игры",
|
||
"lobby.create.description_label": "описание",
|
||
"lobby.create.turn_schedule_label": "расписание ходов",
|
||
"lobby.create.turn_schedule_hint": "cron из пяти полей, например 0 0 * * *",
|
||
"lobby.create.enrollment_ends_at_label": "окончание набора",
|
||
"lobby.create.advanced": "дополнительно",
|
||
"lobby.create.min_players_label": "мин. игроков",
|
||
"lobby.create.max_players_label": "макс. игроков",
|
||
"lobby.create.start_gap_hours_label": "интервал старта (часы)",
|
||
"lobby.create.start_gap_players_label": "интервал старта (игроки)",
|
||
"lobby.create.target_engine_version_label": "версия движка",
|
||
"lobby.create.submit": "создать",
|
||
"lobby.create.submitting": "создаём…",
|
||
"lobby.create.cancel": "отмена",
|
||
"lobby.create.game_name_required": "название игры не должно быть пустым",
|
||
"lobby.create.turn_schedule_required": "расписание ходов не должно быть пустым",
|
||
"lobby.create.enrollment_ends_at_required": "время окончания набора обязательно",
|
||
"lobby.error.invalid_request": "запрос некорректен",
|
||
"lobby.error.subject_not_found": "объект не найден",
|
||
"lobby.error.forbidden": "операция запрещена",
|
||
"lobby.error.conflict": "запрос конфликтует с текущим состоянием",
|
||
"lobby.error.internal_error": "внутренняя ошибка сервера",
|
||
"lobby.error.unknown": "{message}",
|
||
|
||
"game.shell.unknown": "?",
|
||
"game.shell.headline": "{race} @ {game}, ход {turn}",
|
||
"game.shell.connection.online": "онлайн",
|
||
"game.shell.connection.reconnecting": "переподключение…",
|
||
"game.shell.connection.offline": "офлайн",
|
||
"game.shell.menu.toggle_sidebar": "открыть боковую панель",
|
||
"game.shell.menu.close_sidebar": "закрыть боковую панель",
|
||
"game.shell.menu.open_views": "открыть меню видов",
|
||
"game.shell.menu.close_views": "закрыть меню видов",
|
||
"game.shell.menu.account": "аккаунт",
|
||
"game.shell.menu.settings": "настройки",
|
||
"game.shell.menu.sessions": "сессии",
|
||
"game.shell.menu.theme": "тема",
|
||
"game.shell.menu.language": "язык",
|
||
"game.shell.menu.logout": "выйти",
|
||
"game.shell.coming_soon": "скоро будет",
|
||
"game.view.map": "карта",
|
||
"game.view.table": "таблица",
|
||
"game.view.table.planets": "планеты",
|
||
"game.view.table.ship_classes": "классы кораблей",
|
||
"game.view.table.ship_groups": "группы кораблей",
|
||
"game.view.table.fleets": "флоты",
|
||
"game.view.table.sciences": "науки",
|
||
"game.view.table.races": "расы",
|
||
"game.view.report": "отчёт хода",
|
||
"game.view.battle": "журнал боёв",
|
||
"game.view.mail": "дипломатическая почта",
|
||
"game.view.designer.ship_class": "конструктор класса кораблей",
|
||
"game.view.designer.science": "редактор наук",
|
||
"game.sidebar.tab.calculator": "калькулятор",
|
||
"game.sidebar.tab.inspector": "инспектор",
|
||
"game.sidebar.tab.order": "приказ",
|
||
"game.sidebar.empty.calculator": "скоро будет",
|
||
"game.sidebar.empty.inspector": "выберите объект на карте",
|
||
"game.sidebar.empty.order": "приказ пуст",
|
||
"game.sidebar.order.command_delete": "удалить",
|
||
"game.sidebar.order.sync.idle": "нет изменений",
|
||
"game.sidebar.order.sync.in_flight": "синхронизация…",
|
||
"game.sidebar.order.sync.synced": "сохранено на сервере",
|
||
"game.sidebar.order.sync.error": "ошибка синхронизации: {message}",
|
||
"game.sidebar.order.sync.retry": "повторить",
|
||
"game.sidebar.order.status.draft": "черновик",
|
||
"game.sidebar.order.status.valid": "готова",
|
||
"game.sidebar.order.status.invalid": "ошибка",
|
||
"game.sidebar.order.status.submitting": "отправка",
|
||
"game.sidebar.order.status.applied": "принята",
|
||
"game.sidebar.order.status.rejected": "отклонена",
|
||
"game.sidebar.order.label.placeholder": "{label}",
|
||
"game.sidebar.order.label.planet_rename": "переименовать планету {planet} → {name}",
|
||
"game.sidebar.order.label.planet_production": "сменить производство планеты {planet} → {target}",
|
||
"game.bottom_tabs.map": "карта",
|
||
"game.bottom_tabs.calc": "калк",
|
||
"game.bottom_tabs.order": "приказ",
|
||
"game.bottom_tabs.more": "ещё",
|
||
|
||
"game.inspector.planet.kind.local": "ваша планета",
|
||
"game.inspector.planet.kind.other": "планета другой расы",
|
||
"game.inspector.planet.kind.uninhabited": "необитаемая планета",
|
||
"game.inspector.planet.kind.unidentified": "неопознанная планета",
|
||
"game.inspector.planet.field.name": "название",
|
||
"game.inspector.planet.field.owner": "владелец",
|
||
"game.inspector.planet.field.coordinates": "координаты",
|
||
"game.inspector.planet.field.size": "размер",
|
||
"game.inspector.planet.field.population": "население",
|
||
"game.inspector.planet.field.colonists": "колонисты",
|
||
"game.inspector.planet.field.industry": "промышленность",
|
||
"game.inspector.planet.field.industry_stockpile": "запасы промышленности ($)",
|
||
"game.inspector.planet.field.materials_stockpile": "запасы сырья (M)",
|
||
"game.inspector.planet.field.natural_resources": "природные ресурсы",
|
||
"game.inspector.planet.field.production": "текущее производство",
|
||
"game.inspector.planet.field.free_industry": "свободные мощности",
|
||
"game.inspector.planet.production_none": "не задано",
|
||
"game.inspector.planet.unidentified_no_data": "нет данных — известно только местоположение",
|
||
"game.inspector.sheet_close": "закрыть",
|
||
"game.inspector.planet.action.rename": "переименовать",
|
||
"game.inspector.planet.rename.title": "переименование планеты",
|
||
"game.inspector.planet.rename.confirm": "сохранить",
|
||
"game.inspector.planet.rename.cancel": "отмена",
|
||
"game.inspector.planet.rename.invalid.empty": "имя не может быть пустым",
|
||
"game.inspector.planet.rename.invalid.too_long": "имя слишком длинное (максимум 30 символов)",
|
||
"game.inspector.planet.rename.invalid.starts_with_special": "имя не может начинаться со спецсимвола",
|
||
"game.inspector.planet.rename.invalid.ends_with_special": "имя не может заканчиваться спецсимволом",
|
||
"game.inspector.planet.rename.invalid.consecutive_specials": "слишком много спецсимволов подряд",
|
||
"game.inspector.planet.rename.invalid.whitespace": "имя не может содержать пробелы",
|
||
"game.inspector.planet.rename.invalid.disallowed_character": "имя содержит недопустимые символы",
|
||
"game.inspector.planet.production.title": "производство",
|
||
"game.inspector.planet.production.option.industry": "промышленность",
|
||
"game.inspector.planet.production.option.materials": "сырьё",
|
||
"game.inspector.planet.production.option.research": "исследование",
|
||
"game.inspector.planet.production.option.ship": "корабль",
|
||
"game.inspector.planet.production.research.drive": "двигатель",
|
||
"game.inspector.planet.production.research.weapons": "оружие",
|
||
"game.inspector.planet.production.research.shields": "щиты",
|
||
"game.inspector.planet.production.research.cargo": "трюм",
|
||
"game.inspector.planet.production.ship.no_classes": "классы кораблей ещё не спроектированы",
|
||
"game.inspector.planet.cargo.title": "грузовые маршруты",
|
||
"game.inspector.planet.cargo.slot.col": "колонисты",
|
||
"game.inspector.planet.cargo.slot.cap": "промышленность",
|
||
"game.inspector.planet.cargo.slot.mat": "сырьё",
|
||
"game.inspector.planet.cargo.slot.emp": "пустые корабли",
|
||
"game.inspector.planet.cargo.empty": "(маршрута нет)",
|
||
"game.inspector.planet.cargo.add": "добавить",
|
||
"game.inspector.planet.cargo.edit": "изменить",
|
||
"game.inspector.planet.cargo.remove": "удалить",
|
||
"game.inspector.planet.cargo.pick.prompt": "выбери цель на карте (Esc — отмена)",
|
||
"game.inspector.planet.cargo.pick.cancel": "отменить выбор",
|
||
"game.inspector.planet.cargo.pick.no_destinations": "нет планет в зоне полёта {reach} ед.",
|
||
"game.sidebar.order.label.cargo_route_set": "маршрут {loadType} с планеты {source} → планета {destination}",
|
||
"game.sidebar.order.label.cargo_route_remove": "удалить маршрут {loadType} с планеты {source}",
|
||
};
|
||
|
||
export default ru;
|