mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(journey): the journal settings list who a linked trip lets in
Under the contributors, the journey settings on desktop and phone get a section of their own, Through trips, with everyone a trip's journal link lets in, their role and the trip they come through. It is read only there, since the trip's settings manage it, and it leaves out whoever is a contributor already. getJourneyFull answers it as trip_access.
This commit is contained in:
@@ -98,6 +98,19 @@ describe('JourneySettingsDialog', () => {
|
||||
await screen.findByRole('button', { name: /create share link/i })
|
||||
})
|
||||
|
||||
it('FE-JRN-SETTINGS-2683: lists the people a linked trip lets in under their own heading, and nothing without', () => {
|
||||
const { unmount } = mountDialog()
|
||||
expect(screen.queryByText('Through trips')).not.toBeInTheDocument()
|
||||
unmount()
|
||||
|
||||
mountDialog(buildJourney({
|
||||
trip_access: [{ user_id: 7, role: 'viewer', username: 'lena', avatar: null, trip_id: 5, trip_title: 'Italy Trip' }],
|
||||
}))
|
||||
expect(screen.getByText('Through trips')).toBeInTheDocument()
|
||||
expect(screen.getByText('lena')).toBeInTheDocument()
|
||||
expect(screen.getByText('via Italy Trip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-JRN-SETTINGS-002: saves the edited title and subtitle', async () => {
|
||||
const user = userEvent.setup()
|
||||
const { onSaved } = mountDialog()
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DeleteButton, DialogButton, DialogFooter, DialogHeader, DialogSection,
|
||||
import { AddRowButton, EditorField, INPUT, Segmented } from '../shared/dialogParts'
|
||||
import JourneyShareSection from './JourneyShareSection'
|
||||
import { useJourneySettings } from './useJourneySettings'
|
||||
import type { JourneyContributor, JourneyDetail } from '../../store/journeyStore'
|
||||
import type { JourneyContributor, JourneyDetail, JourneyTripAccess } from '../../store/journeyStore'
|
||||
import { pickGradient } from '../../pages/journeyDetail/JourneyDetailPage.helpers'
|
||||
import { AddTripDialog } from './JourneyDetailPageAddTripDialog'
|
||||
import ToggleSwitch from '../Settings/ToggleSwitch'
|
||||
@@ -277,6 +277,8 @@ export function JourneySettingsDialog({ journey, onClose, onSaved, onOpenInvite,
|
||||
</div>
|
||||
<AddRowButton onClick={onOpenInvite}>{t('journey.contributors.invite')}</AddRowButton>
|
||||
</DialogSection>
|
||||
|
||||
<TripAccessSection access={journey.trip_access} roleLabel={roleLabel} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -324,3 +326,32 @@ export function JourneySettingsDialog({ journey, onClose, onSaved, onOpenInvite,
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Who a trip's linked journal lets in (#2683), under its own heading below the
|
||||
* contributors: shown here, but managed in that trip's settings, so read only.
|
||||
*/
|
||||
function TripAccessSection({ access, roleLabel }: {
|
||||
access: JourneyTripAccess[] | undefined
|
||||
roleLabel: (role: JourneyContributor['role']) => string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
if (!access?.length) return null
|
||||
return (
|
||||
<DialogSection label={t('journey.contributors.viaTrips')}>
|
||||
<p className="m-0 mb-2.5 leading-normal text-content-faint" style={fs(11.5)}>{t('journey.contributors.viaTripsHint')}</p>
|
||||
<div className={ROWS}>
|
||||
{access.map(a => (
|
||||
<div key={`${a.user_id}-${a.trip_id}`} className={ROW}>
|
||||
<TripMemberAvatar username={a.username} avatarUrl={avatarSrc(a.avatar)} size={28} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate font-semibold text-content" style={fs(13, 'body')}>{a.username}</div>
|
||||
<div className="truncate text-content-faint" style={fs(11)}>{t('journey.contributors.viaTrip', { trip: a.trip_title })}</div>
|
||||
</div>
|
||||
<span className={`${BADGE} bg-surface-tertiary text-content-muted`} style={fs(10.5)}>{roleLabel(a.role)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogSection>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import {
|
||||
X, Image, Plus, Trash2, UserPlus, Link as LinkIcon,
|
||||
List, Grid3x3, MapPin, Route, Archive, ArchiveRestore, Undo2,
|
||||
} from 'lucide-react'
|
||||
import { X, Image, Plus, Trash2, UserPlus, Link as LinkIcon, List, Grid3x3, MapPin, Route, Archive, ArchiveRestore, Undo2, Link2 } from 'lucide-react'
|
||||
import MSheet from '../../components/MSheet'
|
||||
import MIconBtn from '../../components/MIconBtn'
|
||||
import MToggle from '../../components/MToggle'
|
||||
@@ -237,6 +234,26 @@ export default function MJourneySettingsSheet({
|
||||
{t('journey.contributors.invite')}
|
||||
</button>
|
||||
|
||||
{/* Through a trip's linked journal (#2683): shown, but changed in that trip. */}
|
||||
{!!journey.trip_access?.length && (
|
||||
<>
|
||||
<div className={`${eyebrow} mb-[6px] mt-[14px]`}>{t('journey.contributors.viaTrips')}</div>
|
||||
<p className="m-0 mb-[6px] text-caption text-m-faint">{t('journey.contributors.viaTripsHint')}</p>
|
||||
{journey.trip_access.map(a => (
|
||||
<div key={`${a.user_id}-${a.trip_id}`} className="flex items-center gap-[10px] px-[2px] py-1">
|
||||
<Link2 size={15} className="flex-none text-m-muted" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-caption font-bold">{a.username}</span>
|
||||
<span className="block truncate text-caption text-m-faint">{t('journey.contributors.viaTrip', { trip: a.trip_title })}</span>
|
||||
</span>
|
||||
<span className="rounded-full bg-[color:var(--m-ic)] px-[10px] py-[3px] font-geist text-caption font-extrabold text-m-muted">
|
||||
{a.role}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Public share */}
|
||||
<div className={`${eyebrow} mb-[6px] mt-[14px]`}>{t('journey.share.publicShare')}</div>
|
||||
{!shareLink ? (
|
||||
|
||||
@@ -141,11 +141,23 @@ export interface JourneyContributor {
|
||||
avatar?: string | null
|
||||
}
|
||||
|
||||
/** Someone a trip's linked journal lets in, with the trip they come through (#2683). */
|
||||
export interface JourneyTripAccess {
|
||||
user_id: number
|
||||
role: 'editor' | 'viewer'
|
||||
username: string
|
||||
avatar?: string | null
|
||||
trip_id: number
|
||||
trip_title: string
|
||||
}
|
||||
|
||||
export interface JourneyDetail extends Journey {
|
||||
entries: JourneyEntry[]
|
||||
gallery: GalleryPhoto[]
|
||||
trips: JourneyTrip[]
|
||||
contributors: JourneyContributor[]
|
||||
/** Who the trips' journal links let in besides the contributors, managed in those trips. */
|
||||
trip_access?: JourneyTripAccess[]
|
||||
stats: { entries: number; photos: number; places: number }
|
||||
hide_skeletons?: boolean
|
||||
/** How many suggestions were waved away one at a time, so the settings sheet can offer them back. */
|
||||
|
||||
@@ -89,6 +89,17 @@ describe('MJourneySettingsSheet', () => {
|
||||
expect(screen.getByText('8 places')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-MOB-JSET-2683: lists the people a linked trip lets in, and nothing without', async () => {
|
||||
const { unmount } = setup();
|
||||
expect(screen.queryByText('Through trips')).not.toBeInTheDocument();
|
||||
unmount();
|
||||
|
||||
setup({ trip_access: [{ user_id: 7, role: 'editor', username: 'lena', avatar: null, trip_id: 3, trip_title: 'Tokyo trip' }] });
|
||||
expect(await screen.findByText('Through trips')).toBeInTheDocument();
|
||||
expect(screen.getByText('lena')).toBeInTheDocument();
|
||||
expect(screen.getByText('via Tokyo trip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-MOB-JSET-002: shows the empty hint when no trip is linked', async () => {
|
||||
setup({ trips: [] });
|
||||
expect(await screen.findByText('No trips linked')).toBeInTheDocument();
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { JourneyContributors } from '../entities/JourneyContributors.entity';
|
||||
import type { DB } from '../kysely/db';
|
||||
import { readLinkHideSkeletons, readLinkRole, type TripJournalLinkKyselyDB } from './TripJournalLinks.repository';
|
||||
import {
|
||||
linkReach,
|
||||
readLinkHideSkeletons,
|
||||
readLinkRole,
|
||||
type TripJournalLinkKyselyDB,
|
||||
type TripJournalLinkRole,
|
||||
} from './TripJournalLinks.repository';
|
||||
import { TrekRepository } from './_shared/trek-repository';
|
||||
|
||||
/** JG18's contributor-list row (`getJourneyFull`) — the junction plus the joined username/avatar. */
|
||||
@@ -16,6 +22,16 @@ export interface JourneyContributorListRow {
|
||||
/** The `journey_contributors`/`users` tables {@link JourneyContributorsRepository.listForJourney} reads. */
|
||||
type JourneyContributorListKyselyDB = Pick<DB, 'journey_contributors' | 'users'>;
|
||||
|
||||
/** Someone a trip's journal link lets in, with the trip they come through (#2683). */
|
||||
export interface JourneyTripAccessRow {
|
||||
user_id: number;
|
||||
role: TripJournalLinkRole;
|
||||
username: string;
|
||||
avatar: string | null;
|
||||
trip_id: number;
|
||||
trip_title: string;
|
||||
}
|
||||
|
||||
/** The table the access reads below take a person's own contributor row from. */
|
||||
type JourneyContributorRowKyselyDB = Pick<DB, 'journey_contributors'>;
|
||||
|
||||
@@ -79,6 +95,24 @@ export class JourneyContributorsRepository extends TrekRepository<JourneyContrib
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* #2683 — the people the trips' journal links let into this journal, one row
|
||||
* per trip they come through, leaving out whoever is a contributor already:
|
||||
* the settings show them apart, since they are managed in the trip.
|
||||
*/
|
||||
async listTripAccessForJourney(journeyId: number): Promise<JourneyTripAccessRow[]> {
|
||||
const direct = new Set((await this.listForJourney(journeyId)).map((c) => c.user_id));
|
||||
const rows = await linkReach(this.kysely<TripJournalLinkKyselyDB>())
|
||||
.innerJoin('users as u', 'u.id', 'm.user_id')
|
||||
.select(['m.user_id', 'm.role', 'u.username', 'u.avatar', 't.id as trip_id', 't.title as trip_title'])
|
||||
.where('l.journey_id', '=', journeyId)
|
||||
.orderBy('u.username', 'asc')
|
||||
.execute();
|
||||
return rows
|
||||
.filter((r) => !direct.has(r.user_id))
|
||||
.map((r) => ({ ...r, role: r.role === 'editor' ? 'editor' : 'viewer' }));
|
||||
}
|
||||
|
||||
/** JG18 — `getJourneyFull`'s contributor list, joined to `users` for `username`/`avatar` (`avatar_url` is computed in the SERVICE, unchanged). */
|
||||
async listForJourney(journeyId: number): Promise<JourneyContributorListRow[]> {
|
||||
const rows = await this.kysely<JourneyContributorListKyselyDB>()
|
||||
|
||||
@@ -270,12 +270,10 @@ export class JourneyDomainService {
|
||||
// linked trips (JG17)
|
||||
const trips = await this.journeyTripsRepo.listForJourney(journeyId);
|
||||
|
||||
// contributors (JG18)
|
||||
const contributorsRaw = await this.contributorsRepo.listForJourney(journeyId);
|
||||
const contributors = contributorsRaw.map((c) => ({
|
||||
...c,
|
||||
avatar_url: avatarUrl(c),
|
||||
}));
|
||||
const withAvatar = <R extends { avatar: string | null }>(rows: R[]) => rows.map((r) => ({ ...r, avatar_url: avatarUrl(r) }));
|
||||
const contributors = withAvatar(await this.contributorsRepo.listForJourney(journeyId)); // JG18
|
||||
// Who a trip's journal link lets in besides them (#2683), shown apart in the settings.
|
||||
const tripAccess = withAvatar(await this.contributorsRepo.listTripAccessForJourney(journeyId));
|
||||
|
||||
// stats
|
||||
const entryCount = entries.filter((e) => e.type === 'entry').length;
|
||||
@@ -306,6 +304,7 @@ export class JourneyDomainService {
|
||||
gallery,
|
||||
trips,
|
||||
contributors,
|
||||
trip_access: tripAccess,
|
||||
stats: { entries: entryCount, photos: photoCount, places: places.length },
|
||||
hide_skeletons: hideSkeletons,
|
||||
// What the eye toggle cannot say: how much was waved away one at a time, and
|
||||
|
||||
@@ -171,4 +171,21 @@ describe('TripJournalLinksRepository', () => {
|
||||
expect(await links.findForTrip(s.trip.id)).toBeNull();
|
||||
expect(await links.listMembers(s.trip.id)).toEqual([]);
|
||||
});
|
||||
it('TJLREPO-008: the journal lists who the links let in, per trip, apart from its contributors', async () => {
|
||||
const s = await scene();
|
||||
await links.upsertLink(s.trip.id, s.journal.id, s.owner.id, 1);
|
||||
await links.replaceMembers(
|
||||
s.trip.id,
|
||||
[
|
||||
{ user_id: s.tom.id, role: 'editor' },
|
||||
{ user_id: s.lena.id, role: 'viewer' },
|
||||
],
|
||||
1,
|
||||
);
|
||||
await addJourneyContributor(t, s.journal.id, s.tom.id, 'viewer');
|
||||
|
||||
const access = await contributors.listTripAccessForJourney(s.journal.id);
|
||||
expect(access.map((a) => [a.username, a.role, a.trip_id])).toEqual([['Lena', 'viewer', s.trip.id]]);
|
||||
expect(access[0]?.trip_title).toBe(s.trip.title);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -241,6 +241,9 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'عبر الرحلات',
|
||||
'journey.contributors.viaTripsHint': 'يمكنهم فتح هذه اليوميات لأن رحلة ترتبط بها. غيّر ذلك من إعدادات الرحلة.',
|
||||
'journey.contributors.viaTrip': 'عبر {trip}',
|
||||
'journey.share.publicShare': 'Public Share', // en-fallback
|
||||
'journey.share.createLink': 'Create share link', // en-fallback
|
||||
'journey.share.linkCreated': 'Share link created', // en-fallback
|
||||
|
||||
@@ -206,6 +206,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': '{username} bu səyahət gündəliyindən çıxarılsın?',
|
||||
'journey.contributors.removed': 'İştirakçı çıxarıldı',
|
||||
'journey.contributors.removeFailed': 'İştirakçını çıxarmaq mümkün olmadı',
|
||||
'journey.contributors.viaTrips': 'Səyahətlər vasitəsilə',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Bu gündəliyi aça bilirlər, çünki bir səyahət onu bağlayır. Bunu səyahətin parametrlərində dəyişin.',
|
||||
'journey.contributors.viaTrip': '{trip} vasitəsilə',
|
||||
|
||||
'journey.share.publicShare': 'İctimai paylaşım',
|
||||
'journey.share.createLink': 'Paylaşım linki yarat',
|
||||
|
||||
@@ -265,6 +265,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Por meio de viagens',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Podem abrir este diário porque uma viagem o vincula. Altere isso nas configurações da viagem.',
|
||||
'journey.contributors.viaTrip': 'por {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -265,6 +265,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': "Vols eliminar {username} d'aquesta travesia?",
|
||||
'journey.contributors.removed': 'Col·laborador eliminat',
|
||||
'journey.contributors.removeFailed': "No s'ha pogut eliminar el col·laborador",
|
||||
'journey.contributors.viaTrips': 'A través de viatges',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Poden obrir aquest diari perquè un viatge el vincula. Canvia-ho a la configuració del viatge.',
|
||||
'journey.contributors.viaTrip': 'a través de {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -270,6 +270,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Přes cesty',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Mohou tento deník otevřít, protože ho cesta propojuje. Změňte to v nastavení cesty.',
|
||||
'journey.contributors.viaTrip': 'přes {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -180,6 +180,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': '{username} aus dieser Journey entfernen?',
|
||||
'journey.contributors.removed': 'Mitwirkender entfernt',
|
||||
'journey.contributors.removeFailed': 'Entfernen fehlgeschlagen',
|
||||
'journey.contributors.viaTrips': 'Über Reisen',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Sie können dieses Journal öffnen, weil eine Reise es verknüpft. Ändern lässt sich das in den Einstellungen der Reise.',
|
||||
'journey.contributors.viaTrip': 'über {trip}',
|
||||
'journey.share.publicShare': 'Öffentlicher Link',
|
||||
'journey.share.createLink': 'Link erstellen',
|
||||
'journey.share.linkCreated': 'Link erstellt',
|
||||
|
||||
@@ -188,6 +188,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?',
|
||||
'journey.contributors.removed': 'Contributor removed',
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor',
|
||||
'journey.contributors.viaTrips': 'Through trips',
|
||||
'journey.contributors.viaTripsHint':
|
||||
"They can open this journal because a trip links it. Change that in the trip's settings.",
|
||||
'journey.contributors.viaTrip': 'via {trip}',
|
||||
'journey.share.publicShare': 'Public Share',
|
||||
'journey.share.createLink': 'Create share link',
|
||||
'journey.share.linkCreated': 'Share link created',
|
||||
|
||||
@@ -265,6 +265,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'A través de viajes',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Pueden abrir este diario porque un viaje lo vincula. Cámbialo en los ajustes del viaje.',
|
||||
'journey.contributors.viaTrip': 'a través de {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -190,6 +190,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Kas eemaldada {username} sellest reisiloost?',
|
||||
'journey.contributors.removed': 'Kaasautor eemaldatud',
|
||||
'journey.contributors.removeFailed': 'Kaasautori eemaldamine ebaõnnestus',
|
||||
'journey.contributors.viaTrips': 'Reiside kaudu',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Nad saavad selle päeviku avada, sest reis seob selle. Muuda seda reisi seadetes.',
|
||||
'journey.contributors.viaTrip': '{trip} kaudu',
|
||||
'journey.share.publicShare': 'Avalik jagamine',
|
||||
'journey.share.createLink': 'Loo jagamislink',
|
||||
'journey.share.linkCreated': 'Jagamislink loodud',
|
||||
|
||||
@@ -265,6 +265,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Via des voyages',
|
||||
'journey.contributors.viaTripsHint':
|
||||
"Ces personnes peuvent ouvrir ce journal parce qu'un voyage le lie. Modifiez-le dans les paramètres du voyage.",
|
||||
'journey.contributors.viaTrip': 'via {trip}', // same-as-en
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -183,6 +183,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Αφαίρεση {username} από αυτό το ταξίδι;',
|
||||
'journey.contributors.removed': 'Ο συνεισφέρων αφαιρέθηκε',
|
||||
'journey.contributors.removeFailed': 'Αποτυχία αφαίρεσης συνεισφέροντα',
|
||||
'journey.contributors.viaTrips': 'Μέσω ταξιδιών',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Μπορούν να ανοίξουν αυτό το ημερολόγιο επειδή ένα ταξίδι το συνδέει. Αλλάξτε το στις ρυθμίσεις του ταξιδιού.',
|
||||
'journey.contributors.viaTrip': 'μέσω {trip}',
|
||||
'journey.share.publicShare': 'Δημόσια Κοινοποίηση',
|
||||
'journey.share.createLink': 'Δημιουργία συνδέσμου κοινής χρήσης',
|
||||
'journey.share.linkCreated': 'Ο σύνδεσμος κοινής χρήσης δημιουργήθηκε',
|
||||
|
||||
@@ -264,6 +264,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Utazásokon át',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Azért nyithatják meg ezt a naplót, mert egy utazás kapcsolja. Ezt az utazás beállításaiban módosíthatod.',
|
||||
'journey.contributors.viaTrip': '{trip} útján',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -257,6 +257,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Melalui perjalanan',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Mereka bisa membuka jurnal ini karena sebuah perjalanan menautkannya. Ubah di pengaturan perjalanan.',
|
||||
'journey.contributors.viaTrip': 'melalui {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -264,6 +264,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Tramite viaggi',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Possono aprire questo diario perché un viaggio lo collega. Modificalo nelle impostazioni del viaggio.',
|
||||
'journey.contributors.viaTrip': 'tramite {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -173,6 +173,9 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': '{username}をこの日記から削除しますか?',
|
||||
'journey.contributors.removed': '参加者を削除しました',
|
||||
'journey.contributors.removeFailed': '削除に失敗しました',
|
||||
'journey.contributors.viaTrips': '旅行経由',
|
||||
'journey.contributors.viaTripsHint': '旅行がこのジャーナルをリンクしているため開けます。変更は旅行の設定で行います。',
|
||||
'journey.contributors.viaTrip': '{trip} 経由',
|
||||
'journey.share.publicShare': '公開共有',
|
||||
'journey.share.createLink': '共有リンクを作成',
|
||||
'journey.share.linkCreated': '共有リンクを作成しました',
|
||||
|
||||
@@ -172,6 +172,9 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': '{username}을(를) 이 Journey에서 제거할까요?',
|
||||
'journey.contributors.removed': '기여자가 제거되었습니다',
|
||||
'journey.contributors.removeFailed': '기여자 제거 실패',
|
||||
'journey.contributors.viaTrips': '여행을 통해',
|
||||
'journey.contributors.viaTripsHint': '여행이 이 저널을 연결하고 있어 열 수 있습니다. 여행 설정에서 변경하세요.',
|
||||
'journey.contributors.viaTrip': '{trip} 통해',
|
||||
'journey.share.publicShare': '공개 공유',
|
||||
'journey.share.createLink': '공유 링크 만들기',
|
||||
'journey.share.linkCreated': '공유 링크가 생성되었습니다',
|
||||
|
||||
@@ -266,6 +266,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Via reizen',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Zij kunnen dit dagboek openen omdat een reis het koppelt. Wijzig dat in de instellingen van de reis.',
|
||||
'journey.contributors.viaTrip': 'via {trip}', // same-as-en
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -278,6 +278,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Przez podróże',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Mogą otworzyć ten dziennik, bo podróż go łączy. Zmień to w ustawieniach podróży.',
|
||||
'journey.contributors.viaTrip': 'przez {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -278,6 +278,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': 'Через поездки',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Они могут открыть этот дневник, потому что его привязала поездка. Измените это в настройках поездки.',
|
||||
'journey.contributors.viaTrip': 'через {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -193,6 +193,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Odstrániť {username} z tohto cestovného denníka?',
|
||||
'journey.contributors.removed': 'Prispievateľ odstránený',
|
||||
'journey.contributors.removeFailed': 'Odstránenie prispievateľa zlyhalo',
|
||||
'journey.contributors.viaTrips': 'Cez cesty',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Môžu tento denník otvoriť, pretože ho cesta prepája. Zmeňte to v nastaveniach cesty.',
|
||||
'journey.contributors.viaTrip': 'cez {trip}',
|
||||
'journey.share.publicShare': 'Verejné zdieľanie',
|
||||
'journey.share.createLink': 'Vytvoriť odkaz na zdieľanie',
|
||||
'journey.share.linkCreated': 'Odkaz na zdieľanie vytvorený',
|
||||
|
||||
@@ -181,6 +181,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Ta bort {username} från denna journey?',
|
||||
'journey.contributors.removed': 'Medverkare borttagen',
|
||||
'journey.contributors.removeFailed': 'Kunde inte ta bort medverkare',
|
||||
'journey.contributors.viaTrips': 'Via resor',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'De kan öppna dagboken eftersom en resa kopplar den. Ändra det i resans inställningar.',
|
||||
'journey.contributors.viaTrip': 'via {trip}', // same-as-en
|
||||
'journey.share.publicShare': 'Offentlig delning',
|
||||
'journey.share.createLink': 'Skapa en delningslänk',
|
||||
'journey.share.linkCreated': 'Länk för delning har skapats',
|
||||
|
||||
@@ -183,6 +183,9 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'ลบ {username} ออกจากการเดินทางนี้หรือไม่',
|
||||
'journey.contributors.removed': 'ผู้ร่วมให้ข้อมูลถูกลบออก',
|
||||
'journey.contributors.removeFailed': 'ไม่สามารถลบผู้ร่วมให้ข้อมูลได้',
|
||||
'journey.contributors.viaTrips': 'ผ่านทริป',
|
||||
'journey.contributors.viaTripsHint': 'พวกเขาเปิดบันทึกนี้ได้เพราะทริปหนึ่งเชื่อมไว้ เปลี่ยนได้ในการตั้งค่าของทริป',
|
||||
'journey.contributors.viaTrip': 'ผ่าน {trip}',
|
||||
'journey.share.publicShare': 'แบ่งปันสาธารณะ',
|
||||
'journey.share.createLink': 'สร้างลิงก์แชร์',
|
||||
'journey.share.linkCreated': 'สร้างลิงก์แชร์แล้ว',
|
||||
|
||||
@@ -178,6 +178,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': "{username} bu journey'den kaldırılsın mı?",
|
||||
'journey.contributors.removed': 'Katkıda bulunan kaldırıldı',
|
||||
'journey.contributors.removeFailed': 'Katkıda bulunan kaldırılamadı',
|
||||
'journey.contributors.viaTrips': 'Seyahatler üzerinden',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Bir seyahat bu günlüğü bağladığı için açabiliyorlar. Bunu seyahatin ayarlarından değiştir.',
|
||||
'journey.contributors.viaTrip': '{trip} üzerinden',
|
||||
'journey.share.publicShare': 'Herkese açık paylaşım',
|
||||
'journey.share.createLink': 'Paylaşım bağlantısı oluştur',
|
||||
'journey.share.linkCreated': 'Paylaşım bağlantısı oluşturuldu',
|
||||
|
||||
@@ -187,6 +187,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.remove': 'Вилучити учасника',
|
||||
'journey.contributors.removeConfirm': 'Вилучити {username} з цієї подорожі?',
|
||||
'journey.contributors.removeFailed': 'Не вдалося вилучити учасника',
|
||||
'journey.contributors.viaTrips': 'Через подорожі',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Вони можуть відкрити цей щоденник, бо його привʼязала подорож. Змініть це в налаштуваннях подорожі.',
|
||||
'journey.contributors.viaTrip': 'через {trip}',
|
||||
'journey.contributors.removed': 'Учасника вилучено',
|
||||
'journey.share.publicShare': 'Публічний доступ',
|
||||
'journey.share.createLink': 'Створити посилання для спільного доступу',
|
||||
|
||||
@@ -176,6 +176,10 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Xóa {username} khỏi hành trình này?',
|
||||
'journey.contributors.removed': 'Đã xóa người đóng góp',
|
||||
'journey.contributors.removeFailed': 'Không thể xóa người đóng góp',
|
||||
'journey.contributors.viaTrips': 'Qua chuyến đi',
|
||||
'journey.contributors.viaTripsHint':
|
||||
'Họ có thể mở nhật ký này vì một chuyến đi liên kết nó. Thay đổi trong cài đặt của chuyến đi.',
|
||||
'journey.contributors.viaTrip': 'qua {trip}',
|
||||
'journey.share.publicShare': 'Chia sẻ công khai',
|
||||
'journey.share.createLink': 'Tạo liên kết chia sẻ',
|
||||
'journey.share.linkCreated': 'Đã tạo liên kết chia sẻ',
|
||||
|
||||
@@ -253,6 +253,9 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
|
||||
'journey.contributors.removed': 'Contributor removed', // en-fallback
|
||||
'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
|
||||
'journey.contributors.viaTrips': '透過旅行',
|
||||
'journey.contributors.viaTripsHint': '因為某個旅行連結了此日誌,他們可以開啟它。請在該旅行的設定中變更。',
|
||||
'journey.contributors.viaTrip': '透過 {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
@@ -253,6 +253,9 @@ const journey: TranslationStrings = {
|
||||
'journey.contributors.removeConfirm': '要将 {username} 从此旅程中移除吗?',
|
||||
'journey.contributors.removed': '已移除贡献者',
|
||||
'journey.contributors.removeFailed': '移除贡献者失败',
|
||||
'journey.contributors.viaTrips': '通过旅行',
|
||||
'journey.contributors.viaTripsHint': '因为某个旅行关联了此日志,他们可以打开它。请在该旅行的设置中更改。',
|
||||
'journey.contributors.viaTrip': '通过 {trip}',
|
||||
'journey.editor.externalPhotos': 'External photos', // en-fallback
|
||||
'journey.editor.externalPhotosFor': 'Photos for {date}', // en-fallback
|
||||
'journey.editor.externalPhotosNearby': 'Nearby photos first', // en-fallback
|
||||
|
||||
Reference in New Issue
Block a user