-
+
)
}
+ // A save that the server refused left the dialog open with the button back to
+ // normal and nothing else said, which reads as nothing having happened. The
+ // test verdict wins the space when there is one: it is the more specific
+ // answer and the one the person just asked for.
+ if (!verdict && failure) {
+ return (
+
+
+ {t(`docsync.error.${failure}`)}
+
+ )
+ }
if (!verdict) return
if (verdict.connected) {
return (
diff --git a/client/src/components/Files/docsync/DocSyncPanel.test.tsx b/client/src/components/Files/docsync/DocSyncPanel.test.tsx
index b179417f1..98d8d90ae 100644
--- a/client/src/components/Files/docsync/DocSyncPanel.test.tsx
+++ b/client/src/components/Files/docsync/DocSyncPanel.test.tsx
@@ -347,7 +347,9 @@ describe('DocSyncPanel — what needs a look', () => {
expect(rows).toHaveLength(2)
expect(within(rows[0]).getByText(t('docsync.state.conflict'))).toBeInTheDocument()
expect(within(rows[0]).getByText(t('docsync.issues.conflict'))).toBeInTheDocument()
- expect(within(rows[0]).getByText('2')).toBeInTheDocument()
+ // The conflict row carries a button rather than a bare count: it is the one
+ // of these a person can act on.
+ expect(within(rows[0]).getByRole('button', { name: /Resolve/ })).toBeInTheDocument()
expect(within(rows[1]).getByText(t('docsync.state.too_large'))).toBeInTheDocument()
expect(within(rows[1]).getByText('1')).toBeInTheDocument()
expect(panel.queryByText(t('docsync.state.pending'))).not.toBeInTheDocument()
diff --git a/client/src/components/Files/docsync/DocSyncPanel.tsx b/client/src/components/Files/docsync/DocSyncPanel.tsx
index 51af8268d..2eca9516b 100644
--- a/client/src/components/Files/docsync/DocSyncPanel.tsx
+++ b/client/src/components/Files/docsync/DocSyncPanel.tsx
@@ -4,6 +4,7 @@ import Modal from '../../shared/Modal'
import { useTranslation } from '../../../i18n/TranslationContext'
import { DOCUMENT_PROVIDER_ICONS } from '../../shared/DocumentProviderIcons'
import { StateBadge } from './DocSyncBits'
+import { useConflicts } from './useConflicts'
import { useDocSync, type DocSyncLink, type DocSyncProvider } from './useDocSync'
import DocSyncBinding from './DocSyncBinding'
import DocSyncConnectModal from './DocSyncConnectModal'
@@ -96,7 +97,7 @@ export default function DocSyncPanel({
sync={sync}
isOwner={isOwner}
/>
- {attention > 0 && }
+ {attention > 0 && }
) : (
@@ -260,9 +261,20 @@ function StoreButton({
}
/** What a person still has to decide about, named rather than counted in a chip. */
-function Attention({ counts }: { counts: Record
}) {
+function Attention({
+ counts,
+ tripId,
+ sync,
+}: {
+ counts: Record
+ tripId: number | string
+ sync: ReturnType
+}) {
const { t } = useTranslation()
const rows = ATTENTION_STATES.filter(k => (counts[k] ?? 0) > 0)
+ const [showConflicts, setShowConflicts] = useState(false)
+ const conflicts = useConflicts(tripId, sync, showConflicts)
+
return (
@@ -271,12 +283,55 @@ function Attention({ counts }: { counts: Record }) {
{rows.map(k => (
-
-
- {t(`docsync.state.${k}`)}
- {t(`docsync.issues.${k}`)}
-
- {counts[k]}
+
+
+
+ {t(`docsync.state.${k}`)}
+ {t(`docsync.issues.${k}`)}
+
+ {/* A conflict is the one of these a person can act on, so it is
+ the one that opens. The rest are a state to read. */}
+ {k === 'conflict' ? (
+ setShowConflicts(v => !v)}
+ className="shrink-0 rounded-lg border border-edge px-2.5 py-1 text-caption font-medium text-content transition-colors hover:bg-surface-hover"
+ >
+ {showConflicts ? t('common.close') : t('docsync.conflict.resolve', { count: counts[k] })}
+
+ ) : (
+ {counts[k]}
+ )}
+
+
+ {k === 'conflict' && showConflicts && (
+
+ {conflicts.items === null && (
+
+
+
+ )}
+ {conflicts.items?.map(item => (
+
+ {item.name}
+ {t('docsync.conflict.title')}
+
+ {(['trek', 'provider', 'both'] as const).map(keep => (
+ void conflicts.resolve(item.id, keep)}
+ className="rounded-lg border border-edge px-2.5 py-1 text-caption text-content-secondary transition-colors hover:bg-surface-hover disabled:opacity-50"
+ >
+ {t(`docsync.conflict.${keep === 'trek' ? 'keepTrek' : keep === 'provider' ? 'keepProvider' : 'keepBoth'}`)}
+
+ ))}
+
+
+ ))}
+
+ )}
))}
diff --git a/client/src/components/Files/docsync/DocSyncScopeModal.tsx b/client/src/components/Files/docsync/DocSyncScopeModal.tsx
index 439e73abc..811b76f91 100644
--- a/client/src/components/Files/docsync/DocSyncScopeModal.tsx
+++ b/client/src/components/Files/docsync/DocSyncScopeModal.tsx
@@ -38,6 +38,10 @@ export default function DocSyncScopeModal({
const [working, setWorking] = useState(null)
useEffect(() => {
+ // `sync.loadScopes`, not `sync`: the hook hands back a fresh object on
+ // every render of the panel above, so depending on it re-listed the
+ // provider's folders each time anything up there changed. The callback
+ // itself is stable.
let cancelled = false
void (async () => {
const res = await sync.loadScopes(connection.id)
@@ -46,7 +50,7 @@ export default function DocSyncScopeModal({
setError(res.error ?? null)
})()
return () => { cancelled = true }
- }, [connection.id, sync])
+ }, [connection.id, sync.loadScopes])
const shown = useMemo(() => {
const needle = query.trim().toLowerCase()
diff --git a/client/src/components/Files/docsync/useConflicts.ts b/client/src/components/Files/docsync/useConflicts.ts
new file mode 100644
index 000000000..7e0071f15
--- /dev/null
+++ b/client/src/components/Files/docsync/useConflicts.ts
@@ -0,0 +1,69 @@
+import { useCallback, useEffect, useState } from 'react'
+import { docsyncApi } from '../../../api/client'
+import type { useDocSync } from './useDocSync'
+
+/**
+ * The documents that changed in both places, and the way out of that.
+ *
+ * The server has resolved conflicts since the first version and the strings
+ * have been in the locales just as long, but neither shell ever listed them: a
+ * binding reported "1 conflict" and the only place to act on it was the MCP
+ * tool. So a conflict was a dead end for anyone using the app.
+ *
+ * Shared between the panel and the phone sheet because both list the same rows
+ * with the same three answers, and because the duplication gate counts a second
+ * copy of it against the PR.
+ */
+
+export interface ConflictItem {
+ id: number
+ /** The trip file's name, or the provider's when TREK never got a copy. */
+ name: string
+ remoteName: string | null
+}
+
+interface ItemRow {
+ id: number
+ file_name?: string | null
+ remote_name?: string | null
+}
+
+export function useConflicts(tripId: number | string, sync: ReturnType, open: boolean) {
+ const [items, setItems] = useState(null)
+ const [working, setWorking] = useState(null)
+
+ const reload = useCallback(async () => {
+ const rows = (await docsyncApi.items(tripId, 'conflict')) as ItemRow[]
+ setItems(
+ rows.map(r => ({
+ id: r.id,
+ name: r.file_name || r.remote_name || '',
+ remoteName: r.remote_name ?? null,
+ })),
+ )
+ }, [tripId])
+
+ // Only once the list is on screen: a trip with no conflicts should not pay
+ // for a request to find that out, and the count above it already says so.
+ useEffect(() => {
+ if (open) void reload()
+ }, [open, reload])
+
+ const resolve = useCallback(
+ async (itemId: number, keep: 'trek' | 'provider' | 'both') => {
+ setWorking(itemId)
+ try {
+ await sync.resolveConflict(itemId, keep)
+ // The row is gone from this list either way — it was resolved, or the
+ // call failed and `sync.error` says so. Re-reading is what tells the
+ // difference, rather than removing it here and hoping.
+ await reload()
+ } finally {
+ setWorking(null)
+ }
+ },
+ [sync, reload],
+ )
+
+ return { items, working, resolve, reload }
+}
diff --git a/client/src/components/Files/docsync/useDocSync.ts b/client/src/components/Files/docsync/useDocSync.ts
index da96ec874..e2f63c70b 100644
--- a/client/src/components/Files/docsync/useDocSync.ts
+++ b/client/src/components/Files/docsync/useDocSync.ts
@@ -330,7 +330,23 @@ export function useDocSync(tripId: number | string, enabled: boolean) {
}
}
+/**
+ * The error code a failed call carries, or `unknown`.
+ *
+ * Both shells render this as `docsync.error.`, so anything that is not
+ * one of the codes reaches the screen as a raw translation key — and the server
+ * answers plenty of routes with a sentence ("Link not found") rather than a
+ * code. A general message is worse than a specific one and far better than
+ * `docsync.error.Link not found`.
+ */
function readError(e: unknown): string {
const res = (e as { response?: { data?: { error?: string; message?: string } } })?.response
- return res?.data?.error || res?.data?.message || 'unknown'
+ const raw = res?.data?.error || res?.data?.message || ''
+ // A code passes through — the sync codes and the handful of keys the routes
+ // answer with are all of this shape. A sentence does not: several routes
+ // answer with prose ("Link not found"), and both shells render this as
+ // `docsync.error.`, so that reached the screen as a raw translation
+ // key. A general message is worse than a specific one and far better than
+ // `docsync.error.Link not found`.
+ return /^[a-z0-9_.]+$/i.test(raw) ? raw : 'unknown'
}
diff --git a/client/src/mobile/screens/trip/tabs/MDocSyncSheet.tsx b/client/src/mobile/screens/trip/tabs/MDocSyncSheet.tsx
index bcde38e05..23aff8dbc 100644
--- a/client/src/mobile/screens/trip/tabs/MDocSyncSheet.tsx
+++ b/client/src/mobile/screens/trip/tabs/MDocSyncSheet.tsx
@@ -15,6 +15,8 @@ import {
useDocSync, type DocSyncLink, type DocSyncProvider,
} from '../../../../components/Files/docsync/useDocSync'
import { useConnectForm } from '../../../../components/Files/docsync/useConnectForm'
+import { conflictPolicyKey, nextConflictPolicy } from '../../../../components/Files/docsync/DocSyncBits'
+import { useConflicts } from '../../../../components/Files/docsync/useConflicts'
import { relativeTime } from '../../../../utils/relativeTime'
/**
@@ -123,6 +125,7 @@ export default function MDocSyncSheet({
/>
) : view === 'detail' && link ? (
p.id === link.providerId)?.name ?? link.providerId}
sync={sync}
@@ -224,12 +227,14 @@ function ListView({
/** One binding: the flow, a run button, its settings. */
function DetailView({
link,
+ tripId,
providerName,
sync,
isOwner,
onUnlink,
}: {
link: DocSyncLink
+ tripId: number | string
providerName: string
sync: ReturnType
isOwner: boolean
@@ -299,6 +304,8 @@ function DetailView({
)}
+
+
void sync.syncNow(link.id)}
@@ -331,6 +338,18 @@ function DetailView({
+
+
+ void sync.updateLink(link.id, { conflictPolicy: nextConflictPolicy(link.conflictPolicy) })
+ }
+ className="rounded-full bg-[color:var(--m-ic)] px-3 py-[6px] font-geist text-[0.6875rem] font-bold text-m-ink"
+ >
+ {t(conflictPolicyKey(link.conflictPolicy))}
+
+
+
(null)
useEffect(() => {
+ // `sync.loadScopes`, not `sync`: the hook hands back a fresh object on
+ // every render of the panel above, so depending on it re-listed the
+ // provider's folders each time anything up there changed. The callback
+ // itself is stable.
let cancelled = false
void (async () => {
const res = await sync.loadScopes(connectionId)
@@ -475,7 +498,7 @@ function ScopeView({
setError(res.error ?? null)
})()
return () => { cancelled = true }
- }, [connectionId, sync])
+ }, [connectionId, sync.loadScopes])
const bind = async (scope: { scopeKey: string; label: string; remoteRootId: string | null; remoteRootPath: string | null }) => {
setWorking(scope.scopeKey)
@@ -730,3 +753,70 @@ function slugFor(title: string | undefined, tripId: number | string): string {
.slice(0, 40)
return `${base || 'trek'}-${tripId}`
}
+
+/**
+ * The documents that changed in both places, with the way out.
+ *
+ * The same three answers the panel offers, through the same hook — only the
+ * markup is a phone's.
+ */
+function MConflicts({
+ tripId,
+ sync,
+ count,
+}: {
+ tripId: number | string
+ sync: ReturnType
+ count: number
+}) {
+ const { t } = useTranslation()
+ const [open, setOpen] = useState(false)
+ const conflicts = useConflicts(tripId, sync, open)
+ if (count === 0) return null
+
+ return (
+
+ setOpen(v => !v)}
+ className="flex w-full items-center justify-between gap-3 text-left"
+ >
+
+ {t('docsync.conflict.title')}
+ {t('docsync.issues.conflict')}
+
+
+ {open ? t('common.close') : t('docsync.conflict.resolve', { count })}
+
+
+
+ {open && (
+
+ {conflicts.items === null && (
+
+
+
+ )}
+ {conflicts.items?.map(item => (
+
+ {item.name}
+
+ {(['trek', 'provider', 'both'] as const).map(keep => (
+ void conflicts.resolve(item.id, keep)}
+ className="rounded-full bg-[color:var(--m-ic)] px-3 py-[6px] font-geist text-[0.6875rem] font-bold text-m-ink disabled:opacity-60"
+ >
+ {t(`docsync.conflict.${keep === 'trek' ? 'keepTrek' : keep === 'provider' ? 'keepProvider' : 'keepBoth'}`)}
+
+ ))}
+
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/server/src/nest/doc-sync/doc-sync.helpers.ts b/server/src/nest/doc-sync/doc-sync.helpers.ts
index 5f52de904..3b7c4017b 100644
--- a/server/src/nest/doc-sync/doc-sync.helpers.ts
+++ b/server/src/nest/doc-sync/doc-sync.helpers.ts
@@ -168,6 +168,15 @@ export interface ReconcileInput {
* Nextcloud or OpenCloud binding tripped the mass-delete guard for good.
*/
remoteUnchanged: boolean;
+ /**
+ * What to do when both sides moved since they last agreed.
+ *
+ * `manual` parks the row for a person. The other two are the reason this
+ * field exists: the binding was asked at setup, the answer was stored, and
+ * nothing ever read it — so every conflict was manual whatever the binding
+ * said, and a person who had chosen a side still had to pick it again.
+ */
+ conflictPolicy: 'manual' | 'trek_wins' | 'provider_wins';
/** Providers without stable ids need the rename heuristic. */
stableRemoteIds: boolean;
/** How many failures a row gets before it is shelved for a person to look at. */
@@ -186,7 +195,7 @@ export interface ReconcileInput {
* or ping-pong forever.
*/
export function planReconcile(input: ReconcileInput): ReconcilePlan {
- const { items, remote, local, direction, remoteTruncated, stableRemoteIds, maxAttempts } = input;
+ const { items, remote, local, direction, remoteTruncated, stableRemoteIds, maxAttempts, conflictPolicy } = input;
const now = input.now ?? new Date().toISOString().replace('T', ' ').slice(0, 19);
const actions: PlanAction[] = [];
@@ -223,6 +232,29 @@ export function planReconcile(input: ReconcileInput): ReconcilePlan {
actions.push(action);
};
+ /**
+ * Both sides moved. The binding's answer decides, and `manual` parks it.
+ *
+ * A side that wins takes the whole document, name and bytes together, because
+ * the two halves of one document coming from different sides is not a state
+ * anybody asked for. Direction still has the last word: a pull-only binding
+ * cannot push even when TREK is meant to win, so it parks instead of doing
+ * the opposite of what was chosen.
+ */
+ const settle = (it: SyncItemState, r: RemoteDocument, l: LocalDocument | null): void => {
+ // Only the two answers that name a winner act; anything else — `manual`, a
+ // value from a future version, a column somebody edited by hand — parks the
+ // row. Overwriting one side is the destructive move, so it needs a yes.
+ const park = (): void => { add({ kind: 'conflict', itemId: it.id, remote: r, local: l }); };
+ if (conflictPolicy === 'provider_wins' && direction !== 'push') {
+ return add({ kind: 'pull_update', remote: r, itemId: it.id });
+ }
+ if (conflictPolicy === 'trek_wins' && direction !== 'pull' && l !== null) {
+ return add({ kind: 'push_update', local: l, itemId: it.id, remoteId: it.remoteId });
+ }
+ return park();
+ };
+
const remoteById = new Map();
for (const r of remote) if (!r.isDeleted) remoteById.set(r.remoteId, r);
const localById = new Map();
@@ -310,7 +342,7 @@ export function planReconcile(input: ReconcileInput): ReconcilePlan {
}
if (remoteChanged && !isEcho && localChanged) {
- add({ kind: 'conflict', itemId: it.id, remote: r, local: l ?? null });
+ settle(it, r, l ?? null);
continue;
}
if (remoteChanged && !isEcho) {
@@ -347,9 +379,10 @@ export function planReconcile(input: ReconcileInput): ReconcilePlan {
continue;
}
if (providerRenamed && localRenamed) {
- // Both sides renamed. Nothing here can pick the right one, and picking
- // wrong loses a name somebody chose, so it goes to a person.
- add({ kind: 'conflict', itemId: it.id, remote: r, local: l });
+ // Both sides renamed. Nothing here can pick the right one on its own,
+ // and picking wrong loses a name somebody chose — so it follows the
+ // binding's answer, and goes to a person when there is none.
+ settle(it, r, l);
continue;
}
// No stored name to arbitrate with (a row from before this column, or a
diff --git a/server/src/nest/doc-sync/doc-sync.service.ts b/server/src/nest/doc-sync/doc-sync.service.ts
index c38fdbc50..67dcd22d9 100644
--- a/server/src/nest/doc-sync/doc-sync.service.ts
+++ b/server/src/nest/doc-sync/doc-sync.service.ts
@@ -186,6 +186,7 @@ export class DocSyncService {
remoteUnchanged: listing.data.cursorUnchanged === true,
stableRemoteIds: provider.capabilities(ref).stableId,
maxAttempts: ITEM_MAX_ATTEMPTS,
+ conflictPolicy: link.conflict_policy as 'manual' | 'trek_wins' | 'provider_wins',
});
if (plan.massDeleteGuardTripped) {
diff --git a/server/tests/unit/nest/doc-sync/doc-sync.helpers.test.ts b/server/tests/unit/nest/doc-sync/doc-sync.helpers.test.ts
index cab8eb6d6..314127032 100644
--- a/server/tests/unit/nest/doc-sync/doc-sync.helpers.test.ts
+++ b/server/tests/unit/nest/doc-sync/doc-sync.helpers.test.ts
@@ -65,6 +65,7 @@ const plan = (over: Partial[0]> = {}) =>
direction: 'both',
remoteTruncated: false,
remoteUnchanged: false,
+ conflictPolicy: 'manual',
stableRemoteIds: true,
maxAttempts: 6,
...over,
@@ -195,6 +196,43 @@ describe('planReconcile', () => {
expect(p.actions[0]).toMatchObject({ kind: 'rename_local', name: 'new-upstream.pdf' });
});
+ it('lets the provider win when the binding says so', () => {
+ // The answer was stored at setup and never read: every conflict was manual
+ // whatever the binding said, so a person who had already picked a side was
+ // asked to pick it again, for good.
+ const p = plan({
+ conflictPolicy: 'provider_wins',
+ items: [item({ contentSha256: 'old', remoteVersion: 'v1' })],
+ remote: [remote({ remoteVersion: 'v2' })],
+ local: [local({ sha256: 'newer' })],
+ });
+ expect(p.actions.map((a) => a.kind)).toEqual(['pull_update']);
+ });
+
+ it('lets TREK win when the binding says so', () => {
+ const p = plan({
+ conflictPolicy: 'trek_wins',
+ items: [item({ contentSha256: 'old', remoteVersion: 'v1' })],
+ remote: [remote({ remoteVersion: 'v2' })],
+ local: [local({ sha256: 'newer' })],
+ });
+ expect(p.actions.map((a) => a.kind)).toEqual(['push_update']);
+ });
+
+ it('will not resolve a conflict by doing what the direction forbids', () => {
+ // A pull-only binding cannot push, so "TREK wins" cannot mean "upload it".
+ // Parking is the honest answer; the alternative is silently doing the
+ // opposite of one of the two settings.
+ const p = plan({
+ conflictPolicy: 'trek_wins',
+ direction: 'pull',
+ items: [item({ contentSha256: 'old', remoteVersion: 'v1' })],
+ remote: [remote({ remoteVersion: 'v2' })],
+ local: [local({ sha256: 'newer' })],
+ });
+ expect(p.actions.map((a) => a.kind)).toEqual(['conflict']);
+ });
+
it('asks a person when both sides renamed', () => {
const p = plan({
items: [item({ remoteName: 'old.pdf' })],
diff --git a/shared/src/i18n/ar/docsync.ts b/shared/src/i18n/ar/docsync.ts
index 656fb3a62..5506d44f0 100644
--- a/shared/src/i18n/ar/docsync.ts
+++ b/shared/src/i18n/ar/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'عند حذف مستند',
'docsync.deleteUnlink': 'الإبقاء على النسختين',
'docsync.deleteTrash': 'النقل إلى سلة المهملات',
+ 'docsync.conflictPolicy': 'عند تغيير الجانبين',
+ 'docsync.onConflict.manual': 'اسألني',
+ 'docsync.onConflict.trek_wins': 'الاحتفاظ بنسخة TREK',
+ 'docsync.onConflict.provider_wins': 'الاحتفاظ بنسخة المخزن',
'docsync.webhookHint': 'الصق هذا الـ URL في المزوّد لتصل التغييرات فوراً. بدونه يتحقق TREK على فترات زمنية.',
// حقول نموذج الاتصال. تطابق المفاتيح عمود `label` في
@@ -64,6 +68,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'محذوف في TREK',
'docsync.state.scope_drift': 'نُقل خارج المجلد',
+ 'docsync.conflict.resolve': "حلّ {count}",
+
'docsync.conflict.title': 'تغيّرت النسختان',
'docsync.conflict.keepTrek': 'الإبقاء على نسخة TREK',
'docsync.conflict.keepProvider': 'الإبقاء على نسخة المزوّد',
@@ -127,6 +133,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'متوقفة مؤقتاً',
'docsync.binding.neverRun': 'لم تُشغَّل بعد',
'docsync.binding.deleteHint': 'ما الذي يحدث للنسخة في الجهة الأخرى.',
+ 'docsync.binding.conflictHint': 'أي نسخة تبقى عند تحرير مستند في المكانين.',
'docsync.binding.autoHint': 'التحقق من التغييرات في الخلفية.',
'docsync.binding.webhookTitle': 'تحديثات فورية',
'docsync.binding.copy': 'نسخ',
diff --git a/shared/src/i18n/br/docsync.ts b/shared/src/i18n/br/docsync.ts
index 715f06c8d..6da2cd186 100644
--- a/shared/src/i18n/br/docsync.ts
+++ b/shared/src/i18n/br/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Quando um documento é excluído',
'docsync.deleteUnlink': 'Manter as duas cópias',
'docsync.deleteTrash': 'Mover para a lixeira',
+ 'docsync.conflictPolicy': 'Quando os dois lados mudaram',
+ 'docsync.onConflict.manual': 'Perguntar',
+ 'docsync.onConflict.trek_wins': 'Manter a cópia do TREK',
+ 'docsync.onConflict.provider_wins': 'Manter a cópia do armazenamento',
'docsync.webhookHint':
'Cole esta URL no seu provedor para que as mudanças cheguem na hora. Sem isso, o TREK verifica em intervalos.',
@@ -66,6 +70,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Excluído no TREK',
'docsync.state.scope_drift': 'Saiu da pasta',
+ 'docsync.conflict.resolve': "Resolver {count}",
+
'docsync.conflict.title': 'As duas cópias mudaram',
'docsync.conflict.keepTrek': 'Manter a versão do TREK',
'docsync.conflict.keepProvider': 'Manter a versão do provedor',
@@ -132,6 +138,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Pausado',
'docsync.binding.neverRun': 'ainda não executado',
'docsync.binding.deleteHint': 'O que acontece com a cópia do outro lado.',
+ 'docsync.binding.conflictHint': 'Qual cópia fica quando um documento foi editado nos dois lugares.',
'docsync.binding.autoHint': 'Procurar mudanças em segundo plano.',
'docsync.binding.webhookTitle': 'Atualizações na hora',
'docsync.binding.copy': 'Copiar',
diff --git a/shared/src/i18n/ca/docsync.ts b/shared/src/i18n/ca/docsync.ts
index dd86c3f03..d7381b0ba 100644
--- a/shared/src/i18n/ca/docsync.ts
+++ b/shared/src/i18n/ca/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Quan s’elimina un document',
'docsync.deleteUnlink': 'Conserva les dues còpies',
'docsync.deleteTrash': 'Mou a la paperera',
+ 'docsync.conflictPolicy': 'Quan han canviat les dues bandes',
+ 'docsync.onConflict.manual': "Pregunta-m'ho",
+ 'docsync.onConflict.trek_wins': 'Conserva la còpia del TREK',
+ 'docsync.onConflict.provider_wins': 'Conserva la còpia del magatzem',
'docsync.webhookHint':
'Enganxa aquesta URL al teu proveïdor perquè els canvis arribin de seguida. Sense això, TREK ho comprova cada cert temps.',
@@ -67,6 +71,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Eliminat a TREK',
'docsync.state.scope_drift': 'Ha sortit de la carpeta',
+ 'docsync.conflict.resolve': "Resol {count}",
+
'docsync.conflict.title': 'Han canviat totes dues còpies',
'docsync.conflict.keepTrek': 'Conserva la versió de TREK',
'docsync.conflict.keepProvider': 'Conserva la versió del proveïdor',
@@ -133,6 +139,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'En pausa',
'docsync.binding.neverRun': 'encara no s’ha executat',
'docsync.binding.deleteHint': 'Què passa amb la còpia de l’altra banda.',
+ 'docsync.binding.conflictHint': "Quina còpia es manté quan un document s'ha editat als dos llocs.",
'docsync.binding.autoHint': 'Comprova si hi ha canvis en segon pla.',
'docsync.binding.webhookTitle': 'Actualitzacions instantànies',
'docsync.binding.copy': 'Copia',
diff --git a/shared/src/i18n/cs/docsync.ts b/shared/src/i18n/cs/docsync.ts
index 2272f53b1..1cfeea40e 100644
--- a/shared/src/i18n/cs/docsync.ts
+++ b/shared/src/i18n/cs/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Když se dokument smaže',
'docsync.deleteUnlink': 'Zachovat obě kopie',
'docsync.deleteTrash': 'Přesunout do koše',
+ 'docsync.conflictPolicy': 'Když se změnily obě strany',
+ 'docsync.onConflict.manual': 'Zeptat se',
+ 'docsync.onConflict.trek_wins': 'Ponechat kopii z TREKu',
+ 'docsync.onConflict.provider_wins': 'Ponechat kopii z úložiště',
'docsync.webhookHint':
'Vložte tuto URL k poskytovateli, aby změny přicházely okamžitě. Bez toho se TREK ptá v pravidelných intervalech.',
@@ -65,6 +69,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Smazáno v TREKu',
'docsync.state.scope_drift': 'Přesunuto mimo složku',
+ 'docsync.conflict.resolve': "Vyřešit {count}",
+
'docsync.conflict.title': 'Změnily se obě kopie',
'docsync.conflict.keepTrek': 'Ponechat verzi z TREKu',
'docsync.conflict.keepProvider': 'Ponechat verzi poskytovatele',
@@ -130,6 +136,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Pozastaveno',
'docsync.binding.neverRun': 'zatím neproběhlo',
'docsync.binding.deleteHint': 'Co se stane s kopií na druhé straně.',
+ 'docsync.binding.conflictHint': 'Která kopie zůstane, když byl dokument upraven na obou místech.',
'docsync.binding.autoHint': 'Kontrolovat změny na pozadí.',
'docsync.binding.webhookTitle': 'Okamžité aktualizace',
'docsync.binding.copy': 'Kopírovat',
diff --git a/shared/src/i18n/de/docsync.ts b/shared/src/i18n/de/docsync.ts
index 5f060d15a..f4db0f602 100644
--- a/shared/src/i18n/de/docsync.ts
+++ b/shared/src/i18n/de/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Wenn ein Dokument gelöscht wird',
'docsync.deleteUnlink': 'Beide Kopien behalten',
'docsync.deleteTrash': 'In den Papierkorb verschieben',
+ 'docsync.conflictPolicy': 'Wenn beide Seiten geändert wurden',
+ 'docsync.onConflict.manual': 'Mich fragen',
+ 'docsync.onConflict.trek_wins': 'Die TREK-Kopie behalten',
+ 'docsync.onConflict.provider_wins': 'Die Kopie im Speicher behalten',
'docsync.webhookHint':
'Trage diese URL bei deinem Anbieter ein, damit Änderungen sofort ankommen. Ohne sie fragt TREK in festen Abständen nach.',
@@ -67,6 +71,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'In TREK gelöscht',
'docsync.state.scope_drift': 'Aus dem Ordner verschoben',
+ 'docsync.conflict.resolve': "{count} klären",
+
'docsync.conflict.title': 'Beide Kopien wurden geändert',
'docsync.conflict.keepTrek': 'Die TREK-Version behalten',
'docsync.conflict.keepProvider': 'Die Version des Anbieters behalten',
@@ -132,6 +138,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Pausiert',
'docsync.binding.neverRun': 'noch nie gelaufen',
'docsync.binding.deleteHint': 'Was mit der Kopie auf der anderen Seite geschieht.',
+ 'docsync.binding.conflictHint': 'Welche Kopie bleibt, wenn ein Dokument an beiden Orten bearbeitet wurde.',
'docsync.binding.autoHint': 'Im Hintergrund nach Änderungen suchen.',
'docsync.binding.webhookTitle': 'Sofortige Updates',
'docsync.binding.copy': 'Kopieren',
diff --git a/shared/src/i18n/en/docsync.ts b/shared/src/i18n/en/docsync.ts
index 73c919f6b..3bcfdd553 100644
--- a/shared/src/i18n/en/docsync.ts
+++ b/shared/src/i18n/en/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'When a document is deleted',
'docsync.deleteUnlink': 'Keep both copies',
'docsync.deleteTrash': 'Move to recycle bin',
+ 'docsync.conflictPolicy': 'When both sides changed',
+ 'docsync.onConflict.manual': 'Ask me',
+ 'docsync.onConflict.trek_wins': 'Keep the TREK copy',
+ 'docsync.onConflict.provider_wins': "Keep the store's copy",
'docsync.webhookHint':
'Paste this URL into your provider so changes arrive immediately. Without it, TREK checks on a timer.',
@@ -65,6 +69,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Deleted in TREK',
'docsync.state.scope_drift': 'Moved out of the folder',
+ 'docsync.conflict.resolve': "Resolve {count}",
+
'docsync.conflict.title': 'Both copies changed',
'docsync.conflict.keepTrek': 'Keep the TREK version',
'docsync.conflict.keepProvider': 'Keep the provider version',
@@ -130,6 +136,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Paused',
'docsync.binding.neverRun': 'not run yet',
'docsync.binding.deleteHint': 'What happens to the copy on the other side.',
+ 'docsync.binding.conflictHint': 'Which copy survives when a document was edited in both places.',
'docsync.binding.autoHint': 'Check for changes in the background.',
'docsync.binding.webhookTitle': 'Instant updates',
'docsync.binding.copy': 'Copy',
diff --git a/shared/src/i18n/es/docsync.ts b/shared/src/i18n/es/docsync.ts
index d3711761c..481159eab 100644
--- a/shared/src/i18n/es/docsync.ts
+++ b/shared/src/i18n/es/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Cuando se elimina un documento',
'docsync.deleteUnlink': 'Conservar ambas copias',
'docsync.deleteTrash': 'Mover a la papelera',
+ 'docsync.conflictPolicy': 'Cuando ambos lados cambiaron',
+ 'docsync.onConflict.manual': 'Preguntarme',
+ 'docsync.onConflict.trek_wins': 'Conservar la copia de TREK',
+ 'docsync.onConflict.provider_wins': 'Conservar la copia del almacén',
'docsync.webhookHint':
'Pega esta URL en tu proveedor para que los cambios lleguen de inmediato. Sin ella, TREK comprueba cada cierto tiempo.',
@@ -66,6 +70,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Eliminado en TREK',
'docsync.state.scope_drift': 'Fuera de la carpeta',
+ 'docsync.conflict.resolve': "Resolver {count}",
+
'docsync.conflict.title': 'Las dos copias han cambiado',
'docsync.conflict.keepTrek': 'Conservar la versión de TREK',
'docsync.conflict.keepProvider': 'Conservar la versión del proveedor',
@@ -133,6 +139,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'En pausa',
'docsync.binding.neverRun': 'aún sin ejecutar',
'docsync.binding.deleteHint': 'Qué pasa con la copia del otro lado.',
+ 'docsync.binding.conflictHint': 'Qué copia se queda cuando un documento se editó en los dos sitios.',
'docsync.binding.autoHint': 'Busca cambios en segundo plano.',
'docsync.binding.webhookTitle': 'Actualizaciones instantáneas',
'docsync.binding.copy': 'Copiar',
diff --git a/shared/src/i18n/fr/docsync.ts b/shared/src/i18n/fr/docsync.ts
index 5287cc23e..346731a7a 100644
--- a/shared/src/i18n/fr/docsync.ts
+++ b/shared/src/i18n/fr/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'À la suppression d’un document',
'docsync.deleteUnlink': 'Garder les deux copies',
'docsync.deleteTrash': 'Mettre à la corbeille',
+ 'docsync.conflictPolicy': 'Quand les deux côtés ont changé',
+ 'docsync.onConflict.manual': 'Me demander',
+ 'docsync.onConflict.trek_wins': 'Garder la copie TREK',
+ 'docsync.onConflict.provider_wins': 'Garder la copie du stockage',
'docsync.webhookHint':
'Collez cette URL chez votre fournisseur pour que les changements arrivent immédiatement. Sans cela, TREK vérifie à intervalles réguliers.',
@@ -67,6 +71,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Supprimé dans TREK',
'docsync.state.scope_drift': 'Sorti du dossier',
+ 'docsync.conflict.resolve': "Régler {count}",
+
'docsync.conflict.title': 'Les deux copies ont changé',
'docsync.conflict.keepTrek': 'Garder la version TREK',
'docsync.conflict.keepProvider': 'Garder la version du fournisseur',
@@ -134,6 +140,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'En pause',
'docsync.binding.neverRun': 'jamais exécutée',
'docsync.binding.deleteHint': 'Ce qu’il advient de la copie de l’autre côté.',
+ 'docsync.binding.conflictHint': 'Quelle copie reste quand un document a été modifié des deux côtés.',
'docsync.binding.autoHint': 'Chercher les changements en arrière-plan.',
'docsync.binding.webhookTitle': 'Mises à jour instantanées',
'docsync.binding.copy': 'Copier',
diff --git a/shared/src/i18n/gr/docsync.ts b/shared/src/i18n/gr/docsync.ts
index c656b5da4..84e169736 100644
--- a/shared/src/i18n/gr/docsync.ts
+++ b/shared/src/i18n/gr/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Όταν διαγράφεται ένα έγγραφο',
'docsync.deleteUnlink': 'Διατήρηση και των δύο αντιγράφων',
'docsync.deleteTrash': 'Μετακίνηση στον κάδο ανακύκλωσης',
+ 'docsync.conflictPolicy': 'Όταν άλλαξαν και οι δύο πλευρές',
+ 'docsync.onConflict.manual': 'Να με ρωτάει',
+ 'docsync.onConflict.trek_wins': 'Διατήρηση του αντιγράφου TREK',
+ 'docsync.onConflict.provider_wins': 'Διατήρηση του αντιγράφου του χώρου',
'docsync.webhookHint':
'Επικολλήστε αυτό το URL στον πάροχό σας ώστε οι αλλαγές να φτάνουν αμέσως. Χωρίς αυτό, το TREK ελέγχει ανά τακτά διαστήματα.',
@@ -69,6 +73,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Διαγράφηκε στο TREK',
'docsync.state.scope_drift': 'Μετακινήθηκε εκτός του φακέλου',
+ 'docsync.conflict.resolve': "Επίλυση {count}",
+
'docsync.conflict.title': 'Άλλαξαν και τα δύο αντίγραφα',
'docsync.conflict.keepTrek': 'Διατήρηση της έκδοσης του TREK',
'docsync.conflict.keepProvider': 'Διατήρηση της έκδοσης του παρόχου',
@@ -135,6 +141,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Σε παύση',
'docsync.binding.neverRun': 'δεν έχει εκτελεστεί ακόμη',
'docsync.binding.deleteHint': 'Τι γίνεται με το αντίγραφο στην άλλη πλευρά.',
+ 'docsync.binding.conflictHint': 'Ποιο αντίγραφο μένει όταν ένα έγγραφο άλλαξε και στα δύο σημεία.',
'docsync.binding.autoHint': 'Έλεγχος για αλλαγές στο παρασκήνιο.',
'docsync.binding.webhookTitle': 'Άμεσες ενημερώσεις',
'docsync.binding.copy': 'Αντιγραφή',
diff --git a/shared/src/i18n/hu/docsync.ts b/shared/src/i18n/hu/docsync.ts
index bfdd6cfd8..de45d9db2 100644
--- a/shared/src/i18n/hu/docsync.ts
+++ b/shared/src/i18n/hu/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Ha egy dokumentumot törölnek',
'docsync.deleteUnlink': 'Mindkét példány megtartása',
'docsync.deleteTrash': 'Áthelyezés a kukába',
+ 'docsync.conflictPolicy': 'Ha mindkét oldal változott',
+ 'docsync.onConflict.manual': 'Kérdezzen rá',
+ 'docsync.onConflict.trek_wins': 'A TREK-példány megtartása',
+ 'docsync.onConflict.provider_wins': 'A tároló példányának megtartása',
'docsync.webhookHint':
'Illeszd be ezt az URL-t a szolgáltatódnál, hogy a változások azonnal megérkezzenek. Enélkül a TREK időzítve ellenőriz.',
@@ -68,6 +72,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Törölve a TREK-ben',
'docsync.state.scope_drift': 'Kikerült a mappából',
+ 'docsync.conflict.resolve': "{count} megoldása",
+
'docsync.conflict.title': 'Mindkét példány megváltozott',
'docsync.conflict.keepTrek': 'A TREK-verzió megtartása',
'docsync.conflict.keepProvider': 'A szolgáltató verziójának megtartása',
@@ -134,6 +140,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Szüneteltetve',
'docsync.binding.neverRun': 'még nem futott',
'docsync.binding.deleteHint': 'Mi történjen a másik oldalon lévő példánnyal.',
+ 'docsync.binding.conflictHint': 'Melyik példány marad, ha a dokumentumot mindkét helyen szerkesztették.',
'docsync.binding.autoHint': 'Változások keresése a háttérben.',
'docsync.binding.webhookTitle': 'Azonnali frissítések',
'docsync.binding.copy': 'Másolás',
diff --git a/shared/src/i18n/id/docsync.ts b/shared/src/i18n/id/docsync.ts
index 96d0d5452..fbff663b9 100644
--- a/shared/src/i18n/id/docsync.ts
+++ b/shared/src/i18n/id/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Saat sebuah dokumen dihapus',
'docsync.deleteUnlink': 'Simpan kedua salinan',
'docsync.deleteTrash': 'Pindahkan ke tempat sampah',
+ 'docsync.conflictPolicy': 'Saat kedua sisi berubah',
+ 'docsync.onConflict.manual': 'Tanya saya',
+ 'docsync.onConflict.trek_wins': 'Simpan salinan TREK',
+ 'docsync.onConflict.provider_wins': 'Simpan salinan penyimpanan',
'docsync.webhookHint':
'Tempelkan URL ini di penyediamu agar perubahan langsung tiba. Tanpa itu, TREK memeriksa secara berkala.',
@@ -67,6 +71,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Dihapus di TREK',
'docsync.state.scope_drift': 'Dipindahkan keluar folder',
+ 'docsync.conflict.resolve': "Selesaikan {count}",
+
'docsync.conflict.title': 'Kedua salinan berubah',
'docsync.conflict.keepTrek': 'Pertahankan versi TREK',
'docsync.conflict.keepProvider': 'Pertahankan versi penyedia',
@@ -133,6 +139,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Dijeda',
'docsync.binding.neverRun': 'belum pernah dijalankan',
'docsync.binding.deleteHint': 'Apa yang terjadi pada salinan di sisi satunya.',
+ 'docsync.binding.conflictHint': 'Salinan mana yang bertahan saat dokumen diubah di kedua tempat.',
'docsync.binding.autoHint': 'Periksa perubahan di latar belakang.',
'docsync.binding.webhookTitle': 'Pembaruan seketika',
'docsync.binding.copy': 'Salin',
diff --git a/shared/src/i18n/it/docsync.ts b/shared/src/i18n/it/docsync.ts
index 98b0622f0..990b070e4 100644
--- a/shared/src/i18n/it/docsync.ts
+++ b/shared/src/i18n/it/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Quando un documento viene eliminato',
'docsync.deleteUnlink': 'Mantieni entrambe le copie',
'docsync.deleteTrash': 'Sposta nel cestino',
+ 'docsync.conflictPolicy': 'Quando entrambi i lati sono cambiati',
+ 'docsync.onConflict.manual': 'Chiedimelo',
+ 'docsync.onConflict.trek_wins': 'Tieni la copia di TREK',
+ 'docsync.onConflict.provider_wins': "Tieni la copia dell'archivio",
'docsync.webhookHint':
'Incolla questo URL nel tuo provider per ricevere subito le modifiche. Senza, TREK controlla a intervalli regolari.',
@@ -66,6 +70,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Eliminato in TREK',
'docsync.state.scope_drift': 'Spostato fuori dalla cartella',
+ 'docsync.conflict.resolve': "Risolvi {count}",
+
'docsync.conflict.title': 'Entrambe le copie sono cambiate',
'docsync.conflict.keepTrek': 'Mantieni la versione di TREK',
'docsync.conflict.keepProvider': 'Mantieni la versione del provider',
@@ -132,6 +138,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'In pausa',
'docsync.binding.neverRun': 'mai eseguita',
'docsync.binding.deleteHint': 'Che cosa succede alla copia dall’altra parte.',
+ 'docsync.binding.conflictHint': 'Quale copia resta quando un documento è stato modificato in entrambi i posti.',
'docsync.binding.autoHint': 'Controlla le modifiche in background.',
'docsync.binding.webhookTitle': 'Aggiornamenti immediati',
'docsync.binding.copy': 'Copia',
diff --git a/shared/src/i18n/ja/docsync.ts b/shared/src/i18n/ja/docsync.ts
index a5b9023d3..8539846b1 100644
--- a/shared/src/i18n/ja/docsync.ts
+++ b/shared/src/i18n/ja/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'ドキュメントが削除されたとき',
'docsync.deleteUnlink': '両方のコピーを残す',
'docsync.deleteTrash': 'ゴミ箱へ移動する',
+ 'docsync.conflictPolicy': '両方が変更された場合',
+ 'docsync.onConflict.manual': '確認する',
+ 'docsync.onConflict.trek_wins': 'TREK のコピーを残す',
+ 'docsync.onConflict.provider_wins': '保存先のコピーを残す',
'docsync.webhookHint':
'この URL を連携先に貼り付けると、変更がすぐに届きます。設定しない場合、TREK は一定間隔で確認します。',
@@ -65,6 +69,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'TREK で削除済み',
'docsync.state.scope_drift': 'フォルダー外へ移動',
+ 'docsync.conflict.resolve': "{count} 件を解決",
+
'docsync.conflict.title': '両方のコピーが変更されました',
'docsync.conflict.keepTrek': 'TREK のバージョンを残す',
'docsync.conflict.keepProvider': '連携先のバージョンを残す',
@@ -130,6 +136,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': '一時停止中',
'docsync.binding.neverRun': '未実行',
'docsync.binding.deleteHint': 'もう一方に残るコピーをどう扱うかです。',
+ 'docsync.binding.conflictHint': '両方で編集された文書のうち、どちらのコピーを残すか。',
'docsync.binding.autoHint': 'バックグラウンドで変更を確認します。',
'docsync.binding.webhookTitle': '即時更新',
'docsync.binding.copy': 'コピー',
diff --git a/shared/src/i18n/ko/docsync.ts b/shared/src/i18n/ko/docsync.ts
index 4c2907eb0..43967269c 100644
--- a/shared/src/i18n/ko/docsync.ts
+++ b/shared/src/i18n/ko/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': '문서가 삭제될 때',
'docsync.deleteUnlink': '양쪽 사본 유지',
'docsync.deleteTrash': '휴지통으로 이동',
+ 'docsync.conflictPolicy': '양쪽이 모두 바뀌었을 때',
+ 'docsync.onConflict.manual': '물어보기',
+ 'docsync.onConflict.trek_wins': 'TREK 사본 유지',
+ 'docsync.onConflict.provider_wins': '저장소 사본 유지',
'docsync.webhookHint':
'이 URL을 저장소에 붙여넣으면 변경 사항이 바로 전달됩니다. 설정하지 않으면 TREK이 주기적으로 확인합니다.',
@@ -65,6 +69,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'TREK에서 삭제됨',
'docsync.state.scope_drift': '폴더 밖으로 이동됨',
+ 'docsync.conflict.resolve': "{count}건 해결",
+
'docsync.conflict.title': '양쪽 사본이 모두 바뀌었습니다',
'docsync.conflict.keepTrek': 'TREK 버전 유지',
'docsync.conflict.keepProvider': '저장소 버전 유지',
@@ -129,6 +135,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': '일시 중지됨',
'docsync.binding.neverRun': '아직 실행 안 함',
'docsync.binding.deleteHint': '반대쪽 사본을 어떻게 할지 정합니다.',
+ 'docsync.binding.conflictHint': '양쪽에서 편집된 문서 중 어느 사본을 남길지.',
'docsync.binding.autoHint': '백그라운드에서 변경 사항을 확인합니다.',
'docsync.binding.webhookTitle': '즉시 업데이트',
'docsync.binding.copy': '복사',
diff --git a/shared/src/i18n/nl/docsync.ts b/shared/src/i18n/nl/docsync.ts
index 6e8d55880..987d534bc 100644
--- a/shared/src/i18n/nl/docsync.ts
+++ b/shared/src/i18n/nl/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Als een document wordt verwijderd',
'docsync.deleteUnlink': 'Beide kopieën behouden',
'docsync.deleteTrash': 'Naar de prullenbak verplaatsen',
+ 'docsync.conflictPolicy': 'Als beide kanten zijn gewijzigd',
+ 'docsync.onConflict.manual': 'Vraag het mij',
+ 'docsync.onConflict.trek_wins': 'De TREK-kopie behouden',
+ 'docsync.onConflict.provider_wins': 'De kopie in de opslag behouden',
'docsync.webhookHint':
'Plak deze URL bij je aanbieder, dan komen wijzigingen meteen binnen. Zonder die URL kijkt TREK op vaste tijden.',
@@ -67,6 +71,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Verwijderd in TREK',
'docsync.state.scope_drift': 'Buiten de map verplaatst',
+ 'docsync.conflict.resolve': "{count} oplossen",
+
'docsync.conflict.title': 'Beide kopieën zijn gewijzigd',
'docsync.conflict.keepTrek': 'De TREK-versie behouden',
'docsync.conflict.keepProvider': 'De versie van de aanbieder behouden',
@@ -132,6 +138,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Gepauzeerd',
'docsync.binding.neverRun': 'nog niet uitgevoerd',
'docsync.binding.deleteHint': 'Wat er met de kopie aan de andere kant gebeurt.',
+ 'docsync.binding.conflictHint': 'Welke kopie blijft als een document op beide plekken is bewerkt.',
'docsync.binding.autoHint': 'Op de achtergrond op wijzigingen controleren.',
'docsync.binding.webhookTitle': 'Directe updates',
'docsync.binding.copy': 'Kopiëren',
diff --git a/shared/src/i18n/pl/docsync.ts b/shared/src/i18n/pl/docsync.ts
index 73d4a3e4f..ce401abd7 100644
--- a/shared/src/i18n/pl/docsync.ts
+++ b/shared/src/i18n/pl/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Gdy dokument zostanie usunięty',
'docsync.deleteUnlink': 'Zachowaj obie kopie',
'docsync.deleteTrash': 'Przenieś do kosza',
+ 'docsync.conflictPolicy': 'Gdy zmieniły się obie strony',
+ 'docsync.onConflict.manual': 'Zapytaj mnie',
+ 'docsync.onConflict.trek_wins': 'Zachowaj kopię z TREK-a',
+ 'docsync.onConflict.provider_wins': 'Zachowaj kopię z magazynu',
'docsync.webhookHint':
'Wklej ten adres URL u swojego dostawcy, aby zmiany docierały natychmiast. Bez tego TREK sprawdza je co jakiś czas.',
@@ -66,6 +70,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Usunięty w TREK',
'docsync.state.scope_drift': 'Przeniesiony poza folder',
+ 'docsync.conflict.resolve': "Rozwiąż {count}",
+
'docsync.conflict.title': 'Obie kopie zostały zmienione',
'docsync.conflict.keepTrek': 'Zachowaj wersję z TREK',
'docsync.conflict.keepProvider': 'Zachowaj wersję dostawcy',
@@ -132,6 +138,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Wstrzymane',
'docsync.binding.neverRun': 'jeszcze nie uruchomiono',
'docsync.binding.deleteHint': 'Co dzieje się z kopią po drugiej stronie.',
+ 'docsync.binding.conflictHint': 'Która kopia zostaje, gdy dokument zmieniono w obu miejscach.',
'docsync.binding.autoHint': 'Sprawdzaj zmiany w tle.',
'docsync.binding.webhookTitle': 'Natychmiastowe aktualizacje',
'docsync.binding.copy': 'Kopiuj',
diff --git a/shared/src/i18n/ru/docsync.ts b/shared/src/i18n/ru/docsync.ts
index f723474fe..a29ec1f6e 100644
--- a/shared/src/i18n/ru/docsync.ts
+++ b/shared/src/i18n/ru/docsync.ts
@@ -19,6 +19,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Когда документ удалён',
'docsync.deleteUnlink': 'Оставить обе копии',
'docsync.deleteTrash': 'Переместить в корзину',
+ 'docsync.conflictPolicy': 'Когда изменились обе стороны',
+ 'docsync.onConflict.manual': 'Спросить меня',
+ 'docsync.onConflict.trek_wins': 'Оставить копию TREK',
+ 'docsync.onConflict.provider_wins': 'Оставить копию хранилища',
'docsync.webhookHint':
'Вставьте этот URL у своего провайдера, чтобы изменения приходили сразу. Без него TREK проверяет по таймеру.',
@@ -68,6 +72,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Удалено в TREK',
'docsync.state.scope_drift': 'Перемещено за пределы папки',
+ 'docsync.conflict.resolve': "Решить: {count}",
+
'docsync.conflict.title': 'Изменились обе копии',
'docsync.conflict.keepTrek': 'Оставить версию TREK',
'docsync.conflict.keepProvider': 'Оставить версию провайдера',
@@ -133,6 +139,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Приостановлено',
'docsync.binding.neverRun': 'ещё не запускалась',
'docsync.binding.deleteHint': 'Что происходит с копией на другой стороне.',
+ 'docsync.binding.conflictHint': 'Какая копия останется, если документ изменили в обоих местах.',
'docsync.binding.autoHint': 'Проверять изменения в фоне.',
'docsync.binding.webhookTitle': 'Мгновенные обновления',
'docsync.binding.copy': 'Копировать',
diff --git a/shared/src/i18n/sv/docsync.ts b/shared/src/i18n/sv/docsync.ts
index c8f1f9186..d452d5c00 100644
--- a/shared/src/i18n/sv/docsync.ts
+++ b/shared/src/i18n/sv/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'När ett dokument tas bort',
'docsync.deleteUnlink': 'Behåll båda kopiorna',
'docsync.deleteTrash': 'Flytta till papperskorgen',
+ 'docsync.conflictPolicy': 'När båda sidor ändrats',
+ 'docsync.onConflict.manual': 'Fråga mig',
+ 'docsync.onConflict.trek_wins': 'Behåll TREK-kopian',
+ 'docsync.onConflict.provider_wins': 'Behåll lagrets kopia',
'docsync.webhookHint':
'Klistra in den här URL:en hos din leverantör så kommer ändringar direkt. Utan den kontrollerar TREK med jämna mellanrum.',
@@ -64,6 +68,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Borttagen i TREK',
'docsync.state.scope_drift': 'Flyttad ut ur mappen',
+ 'docsync.conflict.resolve': "Lös {count}",
+
'docsync.conflict.title': 'Båda kopiorna har ändrats',
'docsync.conflict.keepTrek': 'Behåll TREK-versionen',
'docsync.conflict.keepProvider': 'Behåll leverantörens version',
@@ -129,6 +135,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Pausad',
'docsync.binding.neverRun': 'inte körd än',
'docsync.binding.deleteHint': 'Vad som händer med kopian på andra sidan.',
+ 'docsync.binding.conflictHint': 'Vilken kopia som blir kvar när ett dokument redigerats på båda ställena.',
'docsync.binding.autoHint': 'Leta efter ändringar i bakgrunden.',
'docsync.binding.webhookTitle': 'Direkta uppdateringar',
'docsync.binding.copy': 'Kopiera',
diff --git a/shared/src/i18n/tr/docsync.ts b/shared/src/i18n/tr/docsync.ts
index 0a45623ed..c57b18845 100644
--- a/shared/src/i18n/tr/docsync.ts
+++ b/shared/src/i18n/tr/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Bir belge silindiğinde',
'docsync.deleteUnlink': 'İki kopyayı da tut',
'docsync.deleteTrash': 'Geri dönüşüm kutusuna taşı',
+ 'docsync.conflictPolicy': 'İki taraf da değiştiğinde',
+ 'docsync.onConflict.manual': 'Bana sor',
+ 'docsync.onConflict.trek_wins': 'TREK kopyasını tut',
+ 'docsync.onConflict.provider_wins': 'Depodaki kopyayı tut',
'docsync.webhookHint':
'Değişikliklerin hemen ulaşması için bu URL’yi sağlayıcınıza yapıştırın. Bu olmadan TREK belirli aralıklarla denetler.',
@@ -65,6 +69,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'TREK’te silindi',
'docsync.state.scope_drift': 'Klasörün dışına taşındı',
+ 'docsync.conflict.resolve': "{count} tanesini çöz",
+
'docsync.conflict.title': 'İki kopya da değişti',
'docsync.conflict.keepTrek': 'TREK sürümünü tut',
'docsync.conflict.keepProvider': 'Sağlayıcıdaki sürümü tut',
@@ -131,6 +137,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Duraklatıldı',
'docsync.binding.neverRun': 'henüz çalışmadı',
'docsync.binding.deleteHint': 'Karşı taraftaki kopyaya ne olacağı.',
+ 'docsync.binding.conflictHint': 'Belge iki yerde de düzenlendiğinde hangi kopyanın kalacağı.',
'docsync.binding.autoHint': 'Değişiklikleri arka planda denetler.',
'docsync.binding.webhookTitle': 'Anında güncelleme',
'docsync.binding.copy': 'Kopyala',
diff --git a/shared/src/i18n/uk/docsync.ts b/shared/src/i18n/uk/docsync.ts
index 56f9fb357..fe24a396c 100644
--- a/shared/src/i18n/uk/docsync.ts
+++ b/shared/src/i18n/uk/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Коли документ видалено',
'docsync.deleteUnlink': 'Залишити обидві копії',
'docsync.deleteTrash': 'Перемістити до кошика',
+ 'docsync.conflictPolicy': 'Коли змінилися обидві сторони',
+ 'docsync.onConflict.manual': 'Запитати мене',
+ 'docsync.onConflict.trek_wins': 'Залишити копію TREK',
+ 'docsync.onConflict.provider_wins': 'Залишити копію сховища',
'docsync.webhookHint':
'Вставте цю URL-адресу у свого постачальника, щоб зміни надходили одразу. Без цього TREK перевіряє їх за таймером.',
@@ -67,6 +71,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Видалено в TREK',
'docsync.state.scope_drift': 'Переміщено за межі теки',
+ 'docsync.conflict.resolve': "Розв'язати {count}",
+
'docsync.conflict.title': 'Змінилися обидві копії',
'docsync.conflict.keepTrek': 'Залишити версію TREK',
'docsync.conflict.keepProvider': 'Залишити версію постачальника',
@@ -132,6 +138,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Призупинено',
'docsync.binding.neverRun': 'ще не запускалося',
'docsync.binding.deleteHint': 'Що станеться з копією на іншому боці.',
+ 'docsync.binding.conflictHint': 'Яка копія лишиться, якщо документ змінили в обох місцях.',
'docsync.binding.autoHint': 'Перевіряти зміни у фоновому режимі.',
'docsync.binding.webhookTitle': 'Миттєві оновлення',
'docsync.binding.copy': 'Копіювати',
diff --git a/shared/src/i18n/vi/docsync.ts b/shared/src/i18n/vi/docsync.ts
index 0094bd655..5d51ecbb1 100644
--- a/shared/src/i18n/vi/docsync.ts
+++ b/shared/src/i18n/vi/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': 'Khi một tài liệu bị xóa',
'docsync.deleteUnlink': 'Giữ cả hai bản',
'docsync.deleteTrash': 'Chuyển vào thùng rác',
+ 'docsync.conflictPolicy': 'Khi cả hai bên đều thay đổi',
+ 'docsync.onConflict.manual': 'Hỏi tôi',
+ 'docsync.onConflict.trek_wins': 'Giữ bản của TREK',
+ 'docsync.onConflict.provider_wins': 'Giữ bản của kho',
'docsync.webhookHint':
'Dán URL này vào nhà cung cấp của bạn để thay đổi đến ngay lập tức. Nếu không, TREK sẽ kiểm tra theo định kỳ.',
@@ -65,6 +69,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'Đã xóa trong TREK',
'docsync.state.scope_drift': 'Đã chuyển ra khỏi thư mục',
+ 'docsync.conflict.resolve': "Giải quyết {count}",
+
'docsync.conflict.title': 'Cả hai bản đều thay đổi',
'docsync.conflict.keepTrek': 'Giữ bản TREK',
'docsync.conflict.keepProvider': 'Giữ bản của nhà cung cấp',
@@ -130,6 +136,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': 'Đã tạm dừng',
'docsync.binding.neverRun': 'chưa chạy lần nào',
'docsync.binding.deleteHint': 'Điều sẽ xảy ra với bản sao ở phía bên kia.',
+ 'docsync.binding.conflictHint': 'Bản nào được giữ lại khi tài liệu bị sửa ở cả hai nơi.',
'docsync.binding.autoHint': 'Kiểm tra thay đổi ở chế độ nền.',
'docsync.binding.webhookTitle': 'Cập nhật tức thì',
'docsync.binding.copy': 'Sao chép',
diff --git a/shared/src/i18n/zh-TW/docsync.ts b/shared/src/i18n/zh-TW/docsync.ts
index 49a4d4897..b89efcb5a 100644
--- a/shared/src/i18n/zh-TW/docsync.ts
+++ b/shared/src/i18n/zh-TW/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': '當文件被刪除時',
'docsync.deleteUnlink': '兩份都保留',
'docsync.deleteTrash': '移到回收桶',
+ 'docsync.conflictPolicy': '當兩邊都有變更時',
+ 'docsync.onConflict.manual': '詢問我',
+ 'docsync.onConflict.trek_wins': '保留 TREK 的副本',
+ 'docsync.onConflict.provider_wins': '保留儲存端的副本',
'docsync.webhookHint': '把這個 URL 貼到你的提供者,變更就會立刻送達。沒有它,TREK 只會定時檢查。',
// 連線表單欄位。這些鍵對應 document_provider_fields 的 `label` 欄,
@@ -63,6 +67,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': '已在 TREK 中刪除',
'docsync.state.scope_drift': '已移出資料夾',
+ 'docsync.conflict.resolve': "處理 {count} 個",
+
'docsync.conflict.title': '兩份都有變更',
'docsync.conflict.keepTrek': '保留 TREK 版本',
'docsync.conflict.keepProvider': '保留提供者版本',
@@ -125,6 +131,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': '已暫停',
'docsync.binding.neverRun': '尚未執行',
'docsync.binding.deleteHint': '另一邊那份副本會怎麼處理。',
+ 'docsync.binding.conflictHint': '當文件在兩邊都被修改時,保留哪一份副本。',
'docsync.binding.autoHint': '在背景檢查是否有變更。',
'docsync.binding.webhookTitle': '即時更新',
'docsync.binding.copy': '複製',
diff --git a/shared/src/i18n/zh/docsync.ts b/shared/src/i18n/zh/docsync.ts
index edd691b0a..d0a536e95 100644
--- a/shared/src/i18n/zh/docsync.ts
+++ b/shared/src/i18n/zh/docsync.ts
@@ -18,6 +18,10 @@ const docsync: TranslationStrings = {
'docsync.deletePolicy': '当文档被删除时',
'docsync.deleteUnlink': '两份副本都保留',
'docsync.deleteTrash': '移入回收站',
+ 'docsync.conflictPolicy': '当两边都有改动时',
+ 'docsync.onConflict.manual': '询问我',
+ 'docsync.onConflict.trek_wins': '保留 TREK 的副本',
+ 'docsync.onConflict.provider_wins': '保留存储端的副本',
'docsync.webhookHint': '把此 URL 粘贴到你的文档服务中,变更就会立即送达。不设置时,TREK 会定时检查。',
// 连接表单的字段。这些键与 document_provider_fields 表的 `label` 列一一对应,
@@ -63,6 +67,8 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': '已在 TREK 中删除',
'docsync.state.scope_drift': '已移出该文件夹',
+ 'docsync.conflict.resolve': "处理 {count} 个",
+
'docsync.conflict.title': '两份副本都有改动',
'docsync.conflict.keepTrek': '保留 TREK 版本',
'docsync.conflict.keepProvider': '保留文档服务的版本',
@@ -124,6 +130,7 @@ const docsync: TranslationStrings = {
'docsync.binding.autoOff': '已暂停',
'docsync.binding.neverRun': '尚未运行',
'docsync.binding.deleteHint': '另一侧的副本会怎样。',
+ 'docsync.binding.conflictHint': '当文档在两边都被修改时,保留哪一份副本。',
'docsync.binding.autoHint': '在后台检查变更。',
'docsync.binding.webhookTitle': '即时更新',
'docsync.binding.copy': '复制',