Files
TREK/shared/src/i18n/ja/reservations.ts
T
Maurice 56655d53b4 AirTrail integration: import flights & two-way sync (#214) (#1158)
* feat(admin): register AirTrail as an integration addon

Off by default; toggle lives in Admin -> Addons with a Plane icon. The
per-user connection (URL + API key) follows in integration settings.

* feat(integrations): add per-user AirTrail connection

Settings -> Integrations gains an AirTrail section: instance URL + Bearer
API key (encrypted at rest via apiKeyCrypto), a self-signed-TLS opt-in and
a test-connection check. Served by a small Nest controller under
/api/integrations/airtrail, gated on the airtrail addon and SSRF-guarded.
The key is per-user, so it only ever returns that user's own flights.

* feat(transport): import flights from AirTrail

Adds an AirTrail Import button next to Manual Transport that lists the
user's AirTrail flights and highlights the ones inside the trip dates.
Selected flights become reservations linked to their AirTrail origin
(external_* columns), deduped against flights already in the trip, then
broadcast to every member. The mapping resolves airports, airport-local
times and flight metadata; the linkage is what the two-way sync rides on.

* feat(transport): badge AirTrail-linked flights as synced

Linked reservations show an 'AirTrail synced' badge, or 'no longer
synced' once the flight is gone from AirTrail.

* feat(transport): keep TREK and AirTrail flights in sync both ways

A scheduled poll reconciles each connected owner's flights: field edits
(detected by snapshot hash, since AirTrail has no updated_at) flow into
the linked reservation and broadcast live; a flight deleted in AirTrail
keeps the TREK row but stops syncing. Editing a linked flight in TREK
pushes back to AirTrail under the importer's credentials, preserving the
existing seat manifest; if the owner disconnected the link detaches so the
poll can't revert the local edit. Deleting in TREK never touches AirTrail.

* i18n(airtrail): add AirTrail strings across all locales

* test(airtrail): cover flight mapping, timezones and snapshot hashing

* fix(airtrail): reduce airline/aircraft objects to codes

The flight list/get response returns airline and aircraft as joined
objects ({icao, iata, name, ...}), not bare codes. Mapping them straight
through produced '[object Object]' titles and stored objects in metadata,
which crashed reservation rendering. Extract the ICAO/IATA code instead,
and title flights by their flight number.

* fix(airtrail): clear error on non-JSON responses, tolerate /api in URL

A misconfigured instance URL made AirTrail serve its SPA/login HTML, and
the raw JSON.parse failure surfaced as 'Unexpected token <'. Surface an
actionable message instead, and strip a pasted trailing /api so the base
URL still resolves.

* feat(transport): sync AirTrail edits on trip open, not just on the poll

Add a per-user on-demand sync (POST /integrations/airtrail/sync) triggered
when a connected user opens a trip, so AirTrail-side edits appear right away
instead of waiting up to a full poll cycle. Lower the background poll from 15
to 5 minutes as a safety net.

* fix(transport): refresh imported AirTrail flights without a reload

loadTrip doesn't fetch reservations, so a freshly imported flight only
appeared after a full page reload — use loadReservations instead. Also show
flight dates in the user's locale format (e.g. 13.06.2026) rather than the
raw ISO string.

* style(settings): align AirTrail connection with the photo-provider layout

Match the Immich section: stacked URL/key fields, a ToggleSwitch for
self-signed TLS, and a Save / Test-connection row with a status badge.

* feat(transport): add a seat field when editing flights

The transport editor only offered a seat field for trains; flights had
none even though imports store metadata.seat. Show and persist a seat for
flights too.

* style(transport): match the AirTrail button height to Manual Transport

* feat(transport): put the flight seat next to flight number and sync it to AirTrail

Move the seat from a standalone row to the per-leg flight details (beside
the flight number), stored per leg in metadata.legs[].seat with the first
leg mirrored to metadata.seat. On push, set the seat number on the user's
own AirTrail seat (the one with a userId), leaving co-passengers untouched;
import/poll read that same seat back.

* refactor(planner): move the AirTrail trip-open sync into useTripPlanner

Page containers must not own state/effects (lint:pages). Same logic,
relocated from the page into its data hook.

* test(db): pin the region-reconciliation test to its schema version

The test re-ran 'the last migration' assuming the reconciliation is last;
it no longer is once later migrations are appended. Pin to version 135 and
re-run from there (the appended migrations are idempotent).
2026-06-13 13:11:35 +02:00

161 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { TranslationStrings } from '../types';
const reservations: TranslationStrings = {
'reservations.title': '予約',
'reservations.empty': '予約はまだありません',
'reservations.emptyHint': '航空券、ホテルなどの予約を追加しましょう',
'reservations.add': '予約を追加',
'reservations.addManual': '手動予約',
'reservations.placeHint':
'ヒント:予約は場所から直接作成すると、日別計画に紐づけやすくなります。',
'reservations.confirmed': '確定',
'reservations.pending': '保留',
'reservations.summary': '確定 {confirmed}件、保留 {pending}件',
'reservations.fromPlan': '計画から',
'reservations.showFiles': 'ファイルを表示',
'reservations.editTitle': '予約を編集',
'reservations.status': 'ステータス',
'reservations.datetime': '日時',
'reservations.startTime': '開始時刻',
'reservations.endTime': '終了時刻',
'reservations.date': '日付',
'reservations.time': '時間',
'reservations.timeAlt': '時間(代替、例:19:30',
'reservations.notes': 'メモ',
'reservations.notesPlaceholder': '追加のメモ...',
'reservations.meta.airline': '航空会社',
'reservations.meta.flightNumber': '便名',
'reservations.meta.from': '出発地',
'reservations.meta.to': '到着地',
'reservations.layover.route': '経路',
'reservations.layover.stop': '経由地',
'reservations.layover.addStop': '経由地を追加',
'reservations.layover.connection': '乗り継ぎ便',
'reservations.layover.layover': '乗り継ぎ',
'reservations.needsReview': '要確認',
'reservations.needsReviewHint':
'空港を自動で特定できませんでした。場所を確認してください。',
'reservations.searchLocation': '駅・港・住所を検索…',
'reservations.meta.trainNumber': '列車番号',
'reservations.meta.platform': 'ホーム',
'reservations.meta.seat': '座席',
'reservations.meta.checkIn': 'チェックイン',
'reservations.meta.checkOut': 'チェックアウト',
'reservations.meta.linkAccommodation': '宿泊先',
'reservations.meta.checkInUntil': 'チェックイン期限',
'reservations.meta.pickAccommodation': '宿泊先にリンク',
'reservations.meta.noAccommodation': 'なし',
'reservations.meta.hotelPlace': '宿泊先',
'reservations.meta.pickHotel': '宿泊先を選択',
'reservations.meta.fromDay': '開始',
'reservations.meta.toDay': '終了',
'reservations.meta.selectDay': '日を選択',
'reservations.type.flight': '航空便',
'reservations.type.hotel': '宿泊',
'reservations.type.restaurant': 'レストラン',
'reservations.type.train': '列車',
'reservations.type.car': 'レンタカー',
'reservations.type.cruise': 'クルーズ',
'reservations.type.event': 'イベント',
'reservations.type.tour': 'ツアー',
'reservations.type.other': 'その他',
'reservations.type.bus': 'バス',
'reservations.type.ferry': 'フェリー',
'reservations.type.bicycle': '自転車',
'reservations.type.taxi': 'タクシー',
'reservations.type.transport_other': 'その他',
'reservations.confirm.delete': '予約「{name}」を削除しますか?',
'reservations.confirm.deleteTitle': '予約を削除しますか?',
'reservations.confirm.deleteBody': '「{name}」は完全に削除されます。',
'reservations.toast.updated': '予約を更新しました',
'reservations.toast.removed': '予約を削除しました',
'reservations.toast.fileUploaded': 'ファイルをアップロードしました',
'reservations.toast.uploadError': 'アップロードに失敗しました',
'reservations.newTitle': '新しい予約',
'reservations.bookingType': '予約タイプ',
'reservations.titleLabel': 'タイトル',
'reservations.titlePlaceholder': '例:Lufthansa LH123、Hotel Adlon',
'reservations.locationAddress': '場所/住所',
'reservations.locationPlaceholder': '住所、空港、ホテル...',
'reservations.confirmationCode': '予約コード',
'reservations.confirmationPlaceholder': '例:ABC12345',
'reservations.day': '日',
'reservations.noDay': '日なし',
'reservations.place': '場所',
'reservations.noPlace': '場所なし',
'reservations.pendingSave': '保存されます…',
'reservations.uploading': 'アップロード中...',
'reservations.attachFile': 'ファイルを添付',
'reservations.linkExisting': '既存ファイルをリンク',
'reservations.toast.saveError': '保存に失敗しました',
'reservations.toast.updateError': '更新に失敗しました',
'reservations.toast.deleteError': '削除に失敗しました',
'reservations.confirm.remove': '「{name}」の予約を削除しますか?',
'reservations.linkAssignment': '日への割り当てにリンク',
'reservations.pickAssignment': '計画から割り当てを選択...',
'reservations.noAssignment': 'リンクなし(単独)',
'reservations.price': '価格',
'reservations.budgetCategory': '予算カテゴリ',
'reservations.budgetCategoryPlaceholder': '例:交通、宿泊',
'reservations.budgetCategoryAuto': '自動(予約タイプから)',
'reservations.budgetHint': '保存時に予算エントリが自動で作成されます。',
'reservations.departureDate': '出発',
'reservations.arrivalDate': '到着',
'reservations.departureTime': '出発時刻',
'reservations.arrivalTime': '到着時刻',
'reservations.pickupDate': '受取',
'reservations.returnDate': '返却',
'reservations.pickupTime': '受取時刻',
'reservations.returnTime': '返却時刻',
'reservations.endDate': '終了日',
'reservations.meta.departureTimezone': '出発TZ',
'reservations.meta.arrivalTimezone': '到着TZ',
'reservations.span.departure': '出発',
'reservations.span.arrival': '到着',
'reservations.span.inTransit': '移動中',
'reservations.span.pickup': '受取',
'reservations.span.return': '返却',
'reservations.span.active': '有効',
'reservations.span.start': '開始',
'reservations.span.end': '終了',
'reservations.span.ongoing': '進行中',
'reservations.validation.endBeforeStart':
'終了日時は開始日時より後である必要があります',
'reservations.addBooking': '予約を追加',
'reservations.import.title': '予約確認書のインポート',
'reservations.import.cta': 'ファイルからインポート',
'reservations.import.dropHere': '予約確認ファイルをここにドロップするか、クリックして選択',
'reservations.import.dropActive': 'ファイルをドロップしてインポート',
'reservations.import.acceptedFormats': '対応形式:EML、PDF、PKPass、HTML、TXT(各最大 10 MB、最大 5 ファイル)',
'reservations.import.parsing': 'ファイルを解析中…',
'reservations.import.previewHeading': '{count} 件の予約が見つかりました',
'reservations.import.previewEmpty': 'アップロードされたファイルから予約を抽出できませんでした。',
'reservations.import.removeItem': '削除',
'reservations.import.confirm': '{count} 件の予約をインポート',
'reservations.import.back': '戻る',
'reservations.import.success': '{count} 件の予約をインポートしました',
'reservations.import.partialFailure': '{created} 件インポート済み、{failed} 件失敗',
'reservations.import.error': '解析に失敗しました。ファイルが有効な予約確認書であることを確認してください。',
'reservations.import.unavailable': 'このサーバーでは予約インポート機能が利用できません。',
'reservations.import.unsupportedFormat': '対応していないファイル形式です。EML、PDF、PKPass、HTML、または TXT を使用してください。',
'reservations.import.fileTooLarge': 'ファイル「{name}」は 10 MB の制限を超えています。',
'reservations.airtrail.title': 'AirTrail からインポート',
'reservations.airtrail.cta': 'AirTrail',
'reservations.airtrail.synced': 'AirTrail',
'reservations.airtrail.syncedHint': 'AirTrail と同期済み — 編集は双方向で同期されます。',
'reservations.airtrail.notSynced': '未同期',
'reservations.airtrail.notSyncedHint': 'このフライトは AirTrail で削除されたため、同期されなくなりました。',
'reservations.airtrail.loadError': 'AirTrail のフライトを読み込めませんでした。',
'reservations.airtrail.imported': '{count} 件のフライトをインポートしました',
'reservations.airtrail.skippedDuplicate': '{count} 件はこの旅行に既に存在するためスキップしました',
'reservations.airtrail.nothingImported': 'インポートする項目がありません。',
'reservations.airtrail.importError': 'インポートに失敗しました。もう一度お試しください。',
'reservations.airtrail.undo': 'AirTrail からインポート',
'reservations.airtrail.alreadyImported': 'インポート済み',
'reservations.airtrail.duringTrip': 'この旅行の期間中',
'reservations.airtrail.otherFlights': 'その他のフライト',
'reservations.airtrail.empty': 'AirTrail アカウントにフライトが見つかりませんでした。',
'reservations.airtrail.importCta': '{count} 件をインポート',
};
export default reservations;