mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(airtrail): stop labelling a merged multi-leg import "removed in AirTrail"
A multi-leg flight imported "as one flight with a layover" is detached from sync by design — AirTrail has no single flight to round-trip a layover chain to, so it is created with sync_enabled=0 (#1535). The reservations badge collapsed every sync_enabled=0 AirTrail row into "Not synced" with the tooltip "This flight was removed in AirTrail", which is false for a merged booking that was never removed (#1646). Give the multi-leg case (metadata.legs.length > 1) its own state: show the normal AirTrail badge with a truthful tooltip explaining it stays a one-time import. A genuinely removed single-leg flight still shows "Not synced". Adds reservations.airtrail.layoverHint across all locales.
This commit is contained in:
@@ -464,4 +464,33 @@ describe('ReservationsPanel', () => {
|
||||
const text = document.body.textContent || '';
|
||||
expect(text.indexOf('Mid flight')).toBeLessThan(text.indexOf('Hotel stay'));
|
||||
});
|
||||
|
||||
// AirTrail sync badge — three states (#1646)
|
||||
it('FE-PLANNER-RESP-046: a synced AirTrail flight shows the AirTrail badge', () => {
|
||||
const res = buildReservation({ title: 'Synced flight', type: 'flight', external_source: 'airtrail', sync_enabled: 1 } as any);
|
||||
render(<ReservationsPanel {...defaultProps} reservations={[res]} />);
|
||||
expect(screen.getByTitle('Synced from AirTrail — edits stay in sync both ways.')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Not synced')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-RESP-047: a multi-leg import shows the layover hint, not the "removed" message', () => {
|
||||
const res = buildReservation({
|
||||
title: 'Layover flight', type: 'flight', external_source: 'airtrail', sync_enabled: 0,
|
||||
metadata: JSON.stringify({ legs: [{ from: 'AMS' }, { from: 'IST' }] }),
|
||||
} as any);
|
||||
render(<ReservationsPanel {...defaultProps} reservations={[res]} />);
|
||||
// Not falsely labelled "Not synced" / "removed in AirTrail"…
|
||||
expect(screen.queryByText('Not synced')).not.toBeInTheDocument();
|
||||
// …and carries the truthful layover explanation.
|
||||
expect(
|
||||
screen.getByTitle('Imported from AirTrail. A multi-leg flight with a layover has no single AirTrail flight to sync back to, so it stays as a one-time import.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-RESP-048: a single-leg flight removed upstream still shows "Not synced"', () => {
|
||||
const res = buildReservation({ title: 'Removed flight', type: 'flight', external_source: 'airtrail', sync_enabled: 0 } as any);
|
||||
render(<ReservationsPanel {...defaultProps} reservations={[res]} />);
|
||||
expect(screen.getByText('Not synced')).toBeInTheDocument();
|
||||
expect(screen.getByTitle('This flight was removed in AirTrail and no longer syncs.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,6 +97,16 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
|
||||
const typeInfo = getType(r.type)
|
||||
const TypeIcon = typeInfo.Icon
|
||||
const confirmed = r.status === 'confirmed'
|
||||
// A multi-leg AirTrail import is detached from sync *by design* — AirTrail has
|
||||
// no single flight to round-trip a layover chain to, so it's created with
|
||||
// sync_enabled=0 (#1535). Distinguish it from a flight that was removed
|
||||
// upstream so the badge doesn't falsely claim it "was removed in AirTrail" (#1646).
|
||||
const airtrailMultiLeg = r.external_source === 'airtrail' && !r.sync_enabled && (() => {
|
||||
try {
|
||||
const m = typeof r.metadata === 'string' ? JSON.parse(r.metadata || '{}') : (r.metadata || {})
|
||||
return Array.isArray(m?.legs) && m.legs.length > 1
|
||||
} catch { return false }
|
||||
})()
|
||||
const attachedFiles = files.filter(f => f.reservation_id === r.id || (f.linked_reservation_ids || []).includes(r.id))
|
||||
const linked = r.assignment_id ? assignmentLookup[r.assignment_id] : null
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
@@ -196,12 +206,12 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
|
||||
) : null}
|
||||
{r.external_source === 'airtrail' ? (
|
||||
<span
|
||||
className={r.sync_enabled ? 'text-[#2563eb] bg-[rgba(59,130,246,0.12)]' : 'text-content-faint bg-surface-tertiary'}
|
||||
className={r.sync_enabled || airtrailMultiLeg ? 'text-[#2563eb] bg-[rgba(59,130,246,0.12)]' : 'text-content-faint bg-surface-tertiary'}
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 'calc(11px * var(--fs-scale-caption, 1))', fontWeight: 600, padding: '3px 8px', borderRadius: 6 }}
|
||||
title={r.sync_enabled ? t('reservations.airtrail.syncedHint') : t('reservations.airtrail.notSyncedHint')}
|
||||
title={r.sync_enabled ? t('reservations.airtrail.syncedHint') : airtrailMultiLeg ? t('reservations.airtrail.layoverHint') : t('reservations.airtrail.notSyncedHint')}
|
||||
>
|
||||
<Plane size={11} />
|
||||
{r.sync_enabled ? t('reservations.airtrail.synced') : t('reservations.airtrail.notSynced')}
|
||||
{r.sync_enabled || airtrailMultiLeg ? t('reservations.airtrail.synced') : t('reservations.airtrail.notSynced')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'متزامن من AirTrail — تبقى التعديلات متزامنة في الاتجاهين.',
|
||||
'reservations.airtrail.notSynced': 'غير متزامن',
|
||||
'reservations.airtrail.notSyncedHint': 'تمت إزالة هذه الرحلة في AirTrail ولم تعد متزامنة.',
|
||||
'reservations.airtrail.layoverHint': 'تم الاستيراد من AirTrail. الرحلة متعددة المراحل مع توقف لا تقابلها رحلة واحدة في AirTrail للمزامنة معها، لذا تبقى استيرادًا لمرة واحدة.',
|
||||
'reservations.airtrail.loadError': 'تعذّر تحميل رحلاتك من AirTrail.',
|
||||
'reservations.airtrail.imported': 'تم استيراد {count} رحلة/رحلات',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} موجودة بالفعل في هذه الرحلة، تم تخطّيها',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'Sincronizado do AirTrail — as edições permanecem em sincronia nos dois sentidos.',
|
||||
'reservations.airtrail.notSynced': 'Não sincronizado',
|
||||
'reservations.airtrail.notSyncedHint': 'Este voo foi removido no AirTrail e não sincroniza mais.',
|
||||
'reservations.airtrail.layoverHint': 'Importado do AirTrail. Um voo de vários trechos com conexão não tem um único voo do AirTrail para sincronizar, então permanece como uma importação única.',
|
||||
'reservations.airtrail.loadError': 'Não foi possível carregar seus voos do AirTrail.',
|
||||
'reservations.airtrail.imported': '{count} voo(s) importado(s)',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} já nesta viagem, ignorado(s)',
|
||||
|
||||
@@ -155,6 +155,7 @@ const reservations: TranslationStrings = {
|
||||
"Sincronitzat des d'AirTrail — les edicions es mantenen sincronitzades en ambdós sentits.",
|
||||
'reservations.airtrail.notSynced': 'No sincronitzat',
|
||||
'reservations.airtrail.notSyncedHint': "Aquest vol s'ha eliminat a AirTrail i ja no es sincronitza.",
|
||||
'reservations.airtrail.layoverHint': 'Importat des d\'AirTrail. Un vol de diversos trams amb escala no té cap vol únic d\'AirTrail amb què sincronitzar-se, així que es manté com una importació única.',
|
||||
'reservations.airtrail.loadError': "No s'han pogut carregar els teus vols d'AirTrail.",
|
||||
'reservations.airtrail.imported': '{count} vol(s) importat(s)',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} ja existents en aquest viatge, ignorats',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Synchronizováno z AirTrail – úpravy zůstávají synchronní v obou směrech.',
|
||||
'reservations.airtrail.notSynced': 'Nesynchronizováno',
|
||||
'reservations.airtrail.notSyncedHint': 'Tento let byl v AirTrail odstraněn a již se nesynchronizuje.',
|
||||
'reservations.airtrail.layoverHint': 'Importováno z AirTrail. Let s více úseky a mezipřistáním nemá jediný let AirTrail, se kterým by se synchronizoval, takže zůstává jednorázovým importem.',
|
||||
'reservations.airtrail.loadError': 'Vaše lety z AirTrail se nepodařilo načíst.',
|
||||
'reservations.airtrail.imported': 'Importováno letů: {count}',
|
||||
'reservations.airtrail.skippedDuplicate': 'Již v tomto výletu: {count}, přeskočeno',
|
||||
|
||||
@@ -155,6 +155,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Aus AirTrail synchronisiert — Änderungen bleiben in beide Richtungen synchron.',
|
||||
'reservations.airtrail.notSynced': 'Nicht synchronisiert',
|
||||
'reservations.airtrail.notSyncedHint': 'Dieser Flug wurde in AirTrail gelöscht und wird nicht mehr synchronisiert.',
|
||||
'reservations.airtrail.layoverHint': 'Aus AirTrail importiert. Ein mehrteiliger Flug mit Zwischenstopp lässt sich keinem einzelnen AirTrail-Flug zuordnen und bleibt daher ein einmaliger Import.',
|
||||
'reservations.airtrail.loadError': 'Ihre AirTrail-Flüge konnten nicht geladen werden.',
|
||||
'reservations.airtrail.imported': '{count} Flug/Flüge importiert',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} bereits in dieser Reise, übersprungen',
|
||||
|
||||
@@ -151,6 +151,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Synced from AirTrail — edits stay in sync both ways.',
|
||||
'reservations.airtrail.notSynced': 'Not synced',
|
||||
'reservations.airtrail.notSyncedHint': 'This flight was removed in AirTrail and no longer syncs.',
|
||||
'reservations.airtrail.layoverHint': 'Imported from AirTrail. A multi-leg flight with a layover has no single AirTrail flight to sync back to, so it stays as a one-time import.',
|
||||
'reservations.airtrail.loadError': 'Could not load your AirTrail flights.',
|
||||
'reservations.airtrail.imported': '{count} flight(s) imported',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} already in this trip, skipped',
|
||||
|
||||
@@ -153,6 +153,7 @@ const reservations: TranslationStrings = {
|
||||
'Sincronizado desde AirTrail: las ediciones se mantienen sincronizadas en ambos sentidos.',
|
||||
'reservations.airtrail.notSynced': 'No sincronizado',
|
||||
'reservations.airtrail.notSyncedHint': 'Este vuelo se eliminó en AirTrail y ya no se sincroniza.',
|
||||
'reservations.airtrail.layoverHint': 'Importado desde AirTrail. Un vuelo de varios tramos con escala no tiene un único vuelo de AirTrail con el que sincronizarse, por lo que se mantiene como una importación única.',
|
||||
'reservations.airtrail.loadError': 'No se pudieron cargar tus vuelos de AirTrail.',
|
||||
'reservations.airtrail.imported': '{count} vuelo(s) importado(s)',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} ya en este viaje, omitido(s)',
|
||||
|
||||
@@ -156,6 +156,7 @@ const reservations: TranslationStrings = {
|
||||
'Synchronisé depuis AirTrail — les modifications restent synchronisées dans les deux sens.',
|
||||
'reservations.airtrail.notSynced': 'Non synchronisé',
|
||||
'reservations.airtrail.notSyncedHint': "Ce vol a été supprimé dans AirTrail et n'est plus synchronisé.",
|
||||
'reservations.airtrail.layoverHint': 'Importé depuis AirTrail. Un vol à plusieurs segments avec escale ne correspond à aucun vol AirTrail unique à synchroniser ; il reste donc un import ponctuel.',
|
||||
'reservations.airtrail.loadError': 'Impossible de charger vos vols AirTrail.',
|
||||
'reservations.airtrail.imported': '{count} vol(s) importé(s)',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} déjà dans ce voyage, ignoré(s)',
|
||||
|
||||
@@ -154,6 +154,7 @@ const reservations: TranslationStrings = {
|
||||
'Συγχρονισμένο από το AirTrail — οι αλλαγές συγχρονίζονται και προς τις δύο κατευθύνσεις.',
|
||||
'reservations.airtrail.notSynced': 'Μη συγχρονισμένο',
|
||||
'reservations.airtrail.notSyncedHint': 'Αυτή η πτήση αφαιρέθηκε στο AirTrail και δεν συγχρονίζεται πλέον.',
|
||||
'reservations.airtrail.layoverHint': 'Εισαγωγή από το AirTrail. Μια πτήση πολλαπλών σκελών με ενδιάμεση στάση δεν αντιστοιχεί σε μία μόνο πτήση του AirTrail για συγχρονισμό, γι’ αυτό παραμένει εφάπαξ εισαγωγή.',
|
||||
'reservations.airtrail.loadError': 'Δεν ήταν δυνατή η φόρτωση των πτήσεών σας από το AirTrail.',
|
||||
'reservations.airtrail.imported': '{count} πτήση/πτήσεις εισήχθησαν',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} υπάρχουν ήδη σε αυτό το ταξίδι, παραλείφθηκαν',
|
||||
|
||||
@@ -154,6 +154,7 @@ const reservations: TranslationStrings = {
|
||||
'Az AirTrailből szinkronizálva — a módosítások mindkét irányban szinkronban maradnak.',
|
||||
'reservations.airtrail.notSynced': 'Nincs szinkronizálva',
|
||||
'reservations.airtrail.notSyncedHint': 'Ezt a járatot eltávolították az AirTrailből, és többé nem szinkronizálódik.',
|
||||
'reservations.airtrail.layoverHint': 'AirTrailből importálva. A többszakaszos, átszállásos járatnak nincs egyetlen AirTrail-járata, amellyel szinkronizálható lenne, ezért egyszeri importálás marad.',
|
||||
'reservations.airtrail.loadError': 'Nem sikerült betölteni az AirTrail-járataidat.',
|
||||
'reservations.airtrail.imported': '{count} járat importálva',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} már szerepel ebben az utazásban, kihagyva',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Tersinkron dari AirTrail — perubahan tetap sinkron di kedua arah.',
|
||||
'reservations.airtrail.notSynced': 'Tidak tersinkron',
|
||||
'reservations.airtrail.notSyncedHint': 'Penerbangan ini telah dihapus di AirTrail dan tidak lagi tersinkron.',
|
||||
'reservations.airtrail.layoverHint': 'Diimpor dari AirTrail. Penerbangan multi-segmen dengan transit tidak memiliki satu penerbangan AirTrail untuk disinkronkan, sehingga tetap menjadi impor sekali saja.',
|
||||
'reservations.airtrail.loadError': 'Tidak dapat memuat penerbangan AirTrail-mu.',
|
||||
'reservations.airtrail.imported': '{count} penerbangan diimpor',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} sudah ada di perjalanan ini, dilewati',
|
||||
|
||||
@@ -151,6 +151,7 @@ const reservations: TranslationStrings = {
|
||||
'Sincronizzato da AirTrail — le modifiche restano sincronizzate in entrambe le direzioni.',
|
||||
'reservations.airtrail.notSynced': 'Non sincronizzato',
|
||||
'reservations.airtrail.notSyncedHint': 'Questo volo è stato rimosso in AirTrail e non si sincronizza più.',
|
||||
'reservations.airtrail.layoverHint': 'Importato da AirTrail. Un volo con più tratte e scalo non corrisponde a un singolo volo AirTrail da sincronizzare, quindi resta un\'importazione una tantum.',
|
||||
'reservations.airtrail.loadError': 'Impossibile caricare i tuoi voli AirTrail.',
|
||||
'reservations.airtrail.imported': '{count} volo/i importato/i',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} già presente/i in questo viaggio, ignorato/i',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'AirTrail と同期済み — 編集は双方向で同期されます。',
|
||||
'reservations.airtrail.notSynced': '未同期',
|
||||
'reservations.airtrail.notSyncedHint': 'このフライトは AirTrail で削除されたため、同期されなくなりました。',
|
||||
'reservations.airtrail.layoverHint': 'AirTrail からインポートしました。乗り継ぎのある複数区間のフライトは、同期先となる単一の AirTrail フライトがないため、一度限りのインポートのままになります。',
|
||||
'reservations.airtrail.loadError': 'AirTrail のフライトを読み込めませんでした。',
|
||||
'reservations.airtrail.imported': '{count} 件のフライトをインポートしました',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} 件はこの旅行に既に存在するためスキップしました',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'AirTrail에서 동기화됨 — 수정 사항이 양방향으로 동기화됩니다.',
|
||||
'reservations.airtrail.notSynced': '동기화되지 않음',
|
||||
'reservations.airtrail.notSyncedHint': '이 항공편은 AirTrail에서 삭제되어 더 이상 동기화되지 않습니다.',
|
||||
'reservations.airtrail.layoverHint': 'AirTrail에서 가져왔습니다. 경유가 있는 여러 구간 항공편은 동기화할 단일 AirTrail 항공편이 없으므로 일회성 가져오기로 유지됩니다.',
|
||||
'reservations.airtrail.loadError': 'AirTrail 항공편을 불러올 수 없습니다.',
|
||||
'reservations.airtrail.imported': '{count}개 항공편을 가져왔습니다',
|
||||
'reservations.airtrail.skippedDuplicate': '{count}개는 이미 이 여행에 있어 건너뛰었습니다',
|
||||
|
||||
@@ -152,6 +152,7 @@ const reservations: TranslationStrings = {
|
||||
'Gesynchroniseerd vanuit AirTrail — wijzigingen blijven beide kanten op gesynchroniseerd.',
|
||||
'reservations.airtrail.notSynced': 'Niet gesynchroniseerd',
|
||||
'reservations.airtrail.notSyncedHint': 'Deze vlucht is in AirTrail verwijderd en wordt niet meer gesynchroniseerd.',
|
||||
'reservations.airtrail.layoverHint': 'Geïmporteerd uit AirTrail. Een vlucht met meerdere trajecten en overstap heeft geen enkele AirTrail-vlucht om mee te synchroniseren en blijft daarom een eenmalige import.',
|
||||
'reservations.airtrail.loadError': 'Je AirTrail-vluchten konden niet worden geladen.',
|
||||
'reservations.airtrail.imported': '{count} vlucht(en) geïmporteerd',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} al in deze reis, overgeslagen',
|
||||
|
||||
@@ -151,6 +151,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Zsynchronizowano z AirTrail — zmiany są synchronizowane w obie strony.',
|
||||
'reservations.airtrail.notSynced': 'Niezsynchronizowane',
|
||||
'reservations.airtrail.notSyncedHint': 'Ten lot został usunięty w AirTrail i nie jest już synchronizowany.',
|
||||
'reservations.airtrail.layoverHint': 'Zaimportowano z AirTrail. Lot wieloetapowy z przesiadką nie ma pojedynczego lotu AirTrail do synchronizacji, więc pozostaje jednorazowym importem.',
|
||||
'reservations.airtrail.loadError': 'Nie udało się wczytać Twoich lotów z AirTrail.',
|
||||
'reservations.airtrail.imported': 'Zaimportowano {count} lot(y/ów)',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} już w tej wyprawie, pominięto',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Синхронизировано с AirTrail — изменения синхронизируются в обе стороны.',
|
||||
'reservations.airtrail.notSynced': 'Не синхронизировано',
|
||||
'reservations.airtrail.notSyncedHint': 'Этот рейс был удалён в AirTrail и больше не синхронизируется.',
|
||||
'reservations.airtrail.layoverHint': 'Импортировано из AirTrail. Многосегментный рейс с пересадкой не имеет одного рейса AirTrail для синхронизации, поэтому остаётся разовым импортом.',
|
||||
'reservations.airtrail.loadError': 'Не удалось загрузить ваши рейсы из AirTrail.',
|
||||
'reservations.airtrail.imported': 'Импортировано рейсов: {count}',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} уже в этой поездке, пропущено',
|
||||
|
||||
@@ -151,6 +151,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Synkroniserat från AirTrail – ändringarna synkroniseras åt båda hållen.',
|
||||
'reservations.airtrail.notSynced': 'Ej synkroniserad',
|
||||
'reservations.airtrail.notSyncedHint': 'Denna flygning har tagits bort i AirTrail och synkroniseras inte längre.',
|
||||
'reservations.airtrail.layoverHint': 'Importerad från AirTrail. En flerdelad flygning med mellanlandning har ingen enskild AirTrail-flygning att synkronisera mot och förblir därför en engångsimport.',
|
||||
'reservations.airtrail.loadError': 'Det gick inte att hämta dina AirTrail-flygningar.',
|
||||
'reservations.airtrail.imported': '{count} flygning(ar) importerades',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} redan under den här resan, hoppade över',
|
||||
|
||||
@@ -152,6 +152,7 @@ const reservations: TranslationStrings = {
|
||||
'AirTrail ile senkronize edildi — düzenlemeler iki yönlü olarak senkronize kalır.',
|
||||
'reservations.airtrail.notSynced': 'Senkronize değil',
|
||||
'reservations.airtrail.notSyncedHint': "Bu uçuş AirTrail'de kaldırıldı ve artık senkronize edilmiyor.",
|
||||
'reservations.airtrail.layoverHint': 'AirTrail\'den içe aktarıldı. Aktarmalı, çok bacaklı bir uçuşun eşitlenecek tek bir AirTrail uçuşu yoktur, bu nedenle tek seferlik içe aktarma olarak kalır.',
|
||||
'reservations.airtrail.loadError': 'AirTrail uçuşlarınız yüklenemedi.',
|
||||
'reservations.airtrail.imported': '{count} uçuş içe aktarıldı',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} zaten bu gezide, atlandı',
|
||||
|
||||
@@ -150,6 +150,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': 'Синхронізовано з AirTrail — зміни синхронізуються в обидва боки.',
|
||||
'reservations.airtrail.notSynced': 'Не синхронізовано',
|
||||
'reservations.airtrail.notSyncedHint': 'Цей рейс було видалено в AirTrail і він більше не синхронізується.',
|
||||
'reservations.airtrail.layoverHint': 'Імпортовано з AirTrail. Багатосегментний рейс із пересадкою не має єдиного рейсу AirTrail для синхронізації, тому залишається одноразовим імпортом.',
|
||||
'reservations.airtrail.loadError': 'Не вдалося завантажити ваші рейси з AirTrail.',
|
||||
'reservations.airtrail.imported': '{count} рейс(ів) імпортовано',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} вже в цій подорожі, пропущено',
|
||||
|
||||
@@ -153,6 +153,7 @@ const reservations: TranslationStrings = {
|
||||
'Đã đồng bộ hóa từ AirTrail — các chỉnh sửa vẫn được đồng bộ hóa theo cả hai cách.',
|
||||
'reservations.airtrail.notSynced': 'Chưa được đồng bộ hóa',
|
||||
'reservations.airtrail.notSyncedHint': 'Chuyến bay này đã bị xóa trong AirTrail và không còn đồng bộ hóa nữa.',
|
||||
'reservations.airtrail.layoverHint': 'Đã nhập từ AirTrail. Chuyến bay nhiều chặng có điểm nối chuyến không có một chuyến bay AirTrail duy nhất để đồng bộ, nên vẫn là bản nhập một lần.',
|
||||
'reservations.airtrail.loadError': 'Không thể tải chuyến bay AirTrail của bạn.',
|
||||
'reservations.airtrail.imported': '{count} chuyến bay đã nhập',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} đã tham gia chuyến đi này, đã bỏ qua',
|
||||
|
||||
@@ -149,6 +149,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': '已從 AirTrail 同步——編輯會雙向保持同步。',
|
||||
'reservations.airtrail.notSynced': '未同步',
|
||||
'reservations.airtrail.notSyncedHint': '此航班已在 AirTrail 中移除,不再同步。',
|
||||
'reservations.airtrail.layoverHint': '從 AirTrail 匯入。含轉機的多段航班沒有可供同步的單一 AirTrail 航班,因此保留為一次性匯入。',
|
||||
'reservations.airtrail.loadError': '無法載入你的 AirTrail 航班。',
|
||||
'reservations.airtrail.imported': '已匯入 {count} 筆航班',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} 筆已在此行程中,已略過',
|
||||
|
||||
@@ -149,6 +149,7 @@ const reservations: TranslationStrings = {
|
||||
'reservations.airtrail.syncedHint': '已从 AirTrail 同步——编辑会双向保持同步。',
|
||||
'reservations.airtrail.notSynced': '未同步',
|
||||
'reservations.airtrail.notSyncedHint': '此航班已在 AirTrail 中删除,不再同步。',
|
||||
'reservations.airtrail.layoverHint': '从 AirTrail 导入。含中转的多段航班没有对应的单个 AirTrail 航班可供同步,因此保留为一次性导入。',
|
||||
'reservations.airtrail.loadError': '无法加载您的 AirTrail 航班。',
|
||||
'reservations.airtrail.imported': '已导入 {count} 个航班',
|
||||
'reservations.airtrail.skippedDuplicate': '{count} 个已在此行程中,已跳过',
|
||||
|
||||
Reference in New Issue
Block a user